diff --git a/.gitignore b/.gitignore index 7b8edfe..29cda97 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,10 @@ debug-formatter.html # Submodule / nested repos edb +# Repo uses npm; prevent accidental pnpm installs at the root from being committed +# (confuses Vercel's package-manager auto-detection) +/pnpm-lock.yaml + # Misc *.pdf *test* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..50a66cf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "edb"] + path = edb + url = git@github.com:Timidan/edb-extended.git + branch = toolkit diff --git a/README.md b/README.md index becc606..5e67e06 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,47 @@ A full simulation analysis page with six tabs: Browse past simulations stored in IndexedDB. Re-open any previous result for review or further debugging. +### Transaction Analysis (Tx-Captain) + +A forensics sub-tool that turns a completed simulation into a verifiable, evidence-backed verdict. Two entry points: + +- Click **Summarize** in the simulation results header (next to Share/Export). +- Open `/builder?mode=analysis` directly — the panel attaches to whichever simulation is currently pinned in the SimulationContext. + +What it does: + +- **Evidence extraction** -- Walks the V3 trace and emits a structured evidence packet (calls, storage reads/writes, events, transfers, path contracts). +- **Heuristic sieve** -- Flags patterns that routinely ship bugs: `revert_on_path`, `sload_after_sstore` (state contradicting writes), `zero_address_transfer`, `large_delta` (transfers at/over 10 ETH), and `accumulator` (three or more writes to the same contract slot). +- **LLM verdict** -- Sends the sanitized packet to the configured LLM (Gemini / Anthropic / OpenAI / custom, per the LLM config panel) and parses the response against a Zod schema: verdict, confidence, core contradiction, causal chain, gates, risk upper bound, missing evidence. +- **Deep Dive (optional)** -- When enabled, fetches verified Solidity source for each path contract (Sourcify / Etherscan / Blockscout) and falls back to Heimdall decompilation on unverified addresses. Sources are stripped of SPDX/pragma/import lines and vendored libraries (`@openzeppelin/…`, `@chainlink/…`, `node_modules/…`) before being sent to the LLM. + +Privacy and export guarantees: + +- Analyses are stored in a dedicated IndexedDB store (`web3-toolkit-tx-analysis`) separate from simulation history and are never uploaded. +- Sensitive keys (`rpcUrl`, `apiKey`, `privateKey`, `secret`, …) are stripped recursively before persistence. +- Prompts are SHA-256 hashed alongside the verdict so you can audit exactly what was sent. +- Each verdict can be exported as Markdown or JSON from the panel toolbar. + +#### Encrypted Triage (Fhenix Wave 3) + +Alongside the classical LLM path, Tx-Captain ships an **encrypted triage** path built on Fhenix CoFHE. The browser encrypts a 12-bit feature vector derived from the EvidencePacket and submits it to a live fhEVM classifier on Ethereum Sepolia. The contract classifies the vector under FHE, storing encrypted `(classBits, severity)` handles that only the submitter can decrypt via `decryptForView` + self-issued permit. A second contract — `RiskThrottle` — consumes the encrypted severity via `FHE.select` and updates per-user throttle status without any party decrypting on-chain. + +Live deployments (Ethereum Sepolia, chainId 11155111): + +| Contract | Address | Etherscan | +|---|---|---| +| `HackTriage` | `0xBe02be322c7733759ee068067BD620791e9e73D4` | [verified](https://sepolia.etherscan.io/address/0xBe02be322c7733759ee068067BD620791e9e73D4#code) | +| `RiskThrottle` | `0x1FbB01D8e3FE4E99D6742eeBf1A4e276050ecD05` | [verified](https://sepolia.etherscan.io/address/0x1FbB01D8e3FE4E99D6742eeBf1A4e276050ecD05#code) | + +Reproduction: + +1. Set `VITE_HACK_TRIAGE_ADDRESS` in `.env` (defaults to the live address above). +2. Connect a Sepolia-funded wallet via RainbowKit, run a simulation, and open the Analysis panel. +3. Flip the **Cleartext / Encrypted** toggle in `TxAnalysisPanel` to Encrypted, then click **Run Encrypted Triage**. +4. The hook walks `encrypting → writing → waiting-fhe → decrypting → ready`, revealing class labels and severity locally without ever decrypting on-chain. + +Architecture, threat model, leakage table, and open questions live in [docs/superpowers/plans/fhenix-wave3-architecture.md](docs/superpowers/plans/fhenix-wave3-architecture.md). Solidity sources are in [fhe/contracts/](fhe/contracts/); client helpers in [src/utils/hack-analysis/triage/](src/utils/hack-analysis/triage/) and [src/components/tx-analysis/useHackTriage.ts](src/components/tx-analysis/useHackTriage.ts). + ### Integrations The `/integrations` route hosts protocol-specific modules that extend HexKit beyond debugging into active DeFi operations. @@ -175,6 +216,31 @@ For the LI.FI Earn integration and AI concierge, set the following in `.env`: | `GEMINI_FALLBACK_MODEL` | Fallback on 429/503 (default: `gemini-2.5-flash`) | | `PROXY_SECRET` | Shared secret for API proxy authentication (production) | | `ALLOWED_ORIGINS` | Comma-separated allowed CORS origins (production) | +| `VITE_HACK_TRIAGE_ADDRESS` | Ethereum Sepolia `HackTriage` contract (default: `0xBe02...73D4`) | + +### Heimdall endpoints (bridge) + +The bridge exposes three POST endpoints that shell out to the [heimdall-rs](https://github.com/Jon-Becker/heimdall-rs) CLI: + +- `POST /heimdall/version` → `{ available: boolean, version?: string }` +- `POST /heimdall/decompile` → `{ source, abi, bytecodeHash, ... }` + - Body: `{ bytecode: "0x..." }` **or** `{ address, chainId }` +- `POST /heimdall/dump` → `{ slots: [...], address, chainId, blockNumber, ... }` + - Body: `{ address, chainId, blockNumber? | blockTag? }` + +**Security:** Clients must not send `rpcUrl` — the bridge resolves it per-chain from a server-side allowlist (default: mainnet / optimism / base / arbitrum) and rejects any request that carries `rpcUrl` with HTTP 400. Override the allowlist with `HEIMDALL_RPC_BY_CHAIN` (see `.env.example`). The bridge also computes `bytecodeHash` itself from `eth_getCode` — the client never supplies it — to prevent cache-key poisoning. + +Results are cached in-memory on the bridge (LRU + TTL). To install the binary: run `bash edb/scripts/install.sh` — it pulls heimdall via bifrost automatically unless `INSTALL_HEIMDALL=0`. Front end consumers use [`src/utils/heimdall/hooks.ts`](src/utils/heimdall/hooks.ts). + +#### Unverified contract support (heuristic tier) + +When Sourcify has no source for a contract, the Storage Layout Viewer falls back to Heimdall decompilation if the bridge has it installed. Results are shown with an amber **Heuristic** badge and a banner explaining the caveats: + +- Slot labels derived from decompiled function signatures (ERC20-like, Ownable, Pausable) and Heimdall's `modifiers` hint per slot. +- Struct packing is **not** resolved — every populated slot is shown as a single 32-byte value. +- Mapping keys must be discovered manually (use the existing mapping probe). + +To enable: install Heimdall on the bridge (see install step above). To disable globally, set `HEIMDALL_BIN_PATH` to a path that does not exist — the availability check will fall back to "unavailable" and the heuristic tier is skipped. ## Project Structure @@ -229,7 +295,8 @@ src/ lib/ Shared libraries api/ - llm-recommend.ts Gemini LLM proxy with model fallback + llm-invoke.ts Provider-agnostic LLM proxy (allowlist-enforced BYOK transport) + llm-recommend.ts Gemini LLM proxy with model fallback (legacy; kept for rollback) lifi-composer.ts LI.FI Composer quote/execute proxy edb/ EDB simulation API routes diff --git a/api/_llm/allowlist.ts b/api/_llm/allowlist.ts new file mode 100644 index 0000000..9713560 --- /dev/null +++ b/api/_llm/allowlist.ts @@ -0,0 +1,42 @@ +import type { LlmProvider } from "../../src/utils/llm/types"; + +const BASE_URLS: Record, string> = { + anthropic: (process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com").replace(/\/$/, ""), + openai: (process.env.OPENAI_BASE_URL ?? "https://api.openai.com").replace(/\/$/, ""), + gemini: (process.env.GEMINI_BASE_URL ?? "https://generativelanguage.googleapis.com").replace(/\/$/, ""), +}; + +const ALLOWED_PATHS: Record, RegExp[]> = { + anthropic: [/^\/v1\/messages$/], + openai: [/^\/v1\/chat\/completions$/, /^\/v1\/responses$/], + gemini: [ + /^\/v1beta\/models\/[A-Za-z0-9._-]+:generateContent$/, + /^\/v1beta\/models\/[A-Za-z0-9._-]+:streamGenerateContent(\?alt=sse)?$/, + ], +}; + +export function isAllowedProviderUrl( + provider: LlmProvider, + path: string, +): boolean { + if (provider === "custom") return false; + if (!(provider in BASE_URLS)) return false; + if (path.startsWith("http://") || path.startsWith("https://")) return false; + if (path.includes("..")) return false; + const rules = ALLOWED_PATHS[provider]; + return rules.some((rx) => rx.test(path)); +} + +export function resolveProviderUrl( + provider: LlmProvider, + path: string, +): string { + if (!isAllowedProviderUrl(provider, path)) { + throw new Error(`Provider URL not allowed: ${provider}${path}`); + } + return `${BASE_URLS[provider as Exclude]}${path}`; +} + +export function listAllowedProviders(): LlmProvider[] { + return Object.keys(BASE_URLS) as LlmProvider[]; +} diff --git a/api/_llm/guardHeaders.ts b/api/_llm/guardHeaders.ts new file mode 100644 index 0000000..f515518 --- /dev/null +++ b/api/_llm/guardHeaders.ts @@ -0,0 +1,71 @@ +import * as crypto from "crypto"; + +export interface GuardConfig { + allowedOrigins: string[]; + proxySecret: string | undefined; +} + +export interface GuardResult { + ok: boolean; + status?: number; + reason?: string; +} + +function timingSafeEqualStr(a: string, b: string): boolean { + const ab = Buffer.from(a); + const bb = Buffer.from(b); + if (ab.length !== bb.length) return false; + return crypto.timingSafeEqual(ab, bb); +} + +function isSameOriginOrLocalhost(origin: string, host: string | undefined): boolean { + if (origin.startsWith("http://localhost:") || origin === "http://localhost") return true; + if (origin.startsWith("http://127.0.0.1:") || origin === "http://127.0.0.1") return true; + if (!host) return false; + return origin === `https://${host}` || origin === `http://${host}`; +} + +/** + * Fail-closed request guard. Allow order: + * 1. If proxySecret configured, require x-proxy-secret match (timing-safe). + * 2. Else if Origin absent, allow (same-origin server call / curl). + * 3. Else allow when origin is in allowedOrigins, is localhost/127.0.0.1, + * or equals same-host (http(s)://${host}). + * 4. Anything else: reject 403. Empty allowedOrigins + external Origin + * does NOT open the proxy — previously this was a fail-open bug. + */ +export function checkRequestGuards( + req: { headers: Record }, + cfg: GuardConfig, +): GuardResult { + const h = (name: string): string | undefined => { + const v = req.headers[name.toLowerCase()]; + return Array.isArray(v) ? v[0] : v; + }; + + if (cfg.proxySecret) { + const sent = h("x-proxy-secret") ?? ""; + return timingSafeEqualStr(sent, cfg.proxySecret) + ? { ok: true } + : { ok: false, status: 403, reason: "bad_proxy_secret" }; + } + + const origin = h("origin"); + const host = h("host"); + + if (!origin) return { ok: true }; + + if (cfg.allowedOrigins.includes(origin)) return { ok: true }; + if (isSameOriginOrLocalhost(origin, host)) return { ok: true }; + + return { ok: false, status: 403, reason: "origin_not_allowed" }; +} + +export function readGuardConfigFromEnv(): GuardConfig { + const allowedOrigins = (process.env.ALLOWED_ORIGINS ?? "") + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + const proxySecret = process.env.PROXY_SECRET || undefined; + return { allowedOrigins, proxySecret }; +} diff --git a/api/edb/[...path].ts b/api/edb-proxy.ts similarity index 92% rename from api/edb/[...path].ts rename to api/edb-proxy.ts index 3ab54ed..cbcf8d9 100644 --- a/api/edb/[...path].ts +++ b/api/edb-proxy.ts @@ -1,5 +1,5 @@ import type { VercelRequest, VercelResponse } from "@vercel/node"; -import { maybeInjectDefaultEtherscanKey } from "../edbShared.js"; +import { maybeInjectDefaultEtherscanKey } from "./edbShared.js"; export const config = { api: { bodyParser: false }, @@ -104,9 +104,17 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { return res.status(405).json({ error: "method_not_allowed" }); } - // Extract sub-path from URL — more reliable than req.query.path across Vercel runtimes - const urlPath = (req.url || "").split("?")[0]; - const subPath = urlPath.replace(/^\/api\/edb\/?/, ""); + // Extract sub-path from the `path` query param populated by the Vercel + // rewrite rule in vercel.json. Vercel's file-based catch-all routing + // (`api/edb/[...path].ts`) does not reliably match multi-segment requests + // under `/api/edb/*` on this project, so we route via an explicit rewrite + // that mirrors the lifi-composer pattern. + const pathParam = req.query?.path; + const subPath = Array.isArray(pathParam) + ? pathParam.join("/") + : typeof pathParam === "string" + ? pathParam + : ""; // Validate each path segment const parts = subPath ? subPath.split("/") : []; diff --git a/api/llm-invoke.ts b/api/llm-invoke.ts new file mode 100644 index 0000000..d3979b2 --- /dev/null +++ b/api/llm-invoke.ts @@ -0,0 +1,195 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; +import * as crypto from "crypto"; +import { resolveProviderUrl, isAllowedProviderUrl } from "./_llm/allowlist"; +import { checkRequestGuards, readGuardConfigFromEnv } from "./_llm/guardHeaders"; +import type { LlmProvider } from "../src/utils/llm/types"; + +export const config = { + api: { bodyParser: true }, + maxDuration: 60, +}; + +interface InvokeBody { + provider: LlmProvider; + path: string; + body: Record; +} + +const ALLOWED_ORIGINS = new Set( + (process.env.ALLOWED_ORIGINS || "").split(",").filter(Boolean), +); +const PROXY_SECRET = process.env.PROXY_SECRET || ""; +const MAX_BODY_BYTES = 64 * 1024; +const UPSTREAM_TIMEOUT_MS = 55_000; + +function getAllowedOrigin(req: VercelRequest): string | null { + const origin = req.headers.origin; + if (!origin) return null; + if (ALLOWED_ORIGINS.has(origin)) return origin; + if (origin.startsWith("http://localhost:")) return origin; + const host = req.headers.host; + if (host && origin === `https://${host}`) return origin; + return null; +} + +function hasValidSecret(req: VercelRequest): boolean { + if (!PROXY_SECRET) return false; + const header = req.headers["x-proxy-secret"]; + if (typeof header !== "string") return false; + const a = Buffer.from(header); + const b = Buffer.from(PROXY_SECRET); + if (a.length !== b.length) return false; + return crypto.timingSafeEqual(a, b); +} + +function setCorsHeaders(res: VercelResponse, allowedOrigin: string | null) { + if (allowedOrigin) res.setHeader("Access-Control-Allow-Origin", allowedOrigin); + res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); + res.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, x-proxy-secret, x-user-api-key", + ); +} + +function providerAuthHeaders( + provider: LlmProvider, + userKey: string | undefined, +): Record { + const key = userKey && userKey.length > 0 ? userKey : ""; + switch (provider) { + case "anthropic": + return key + ? { "x-api-key": key, "anthropic-version": "2023-06-01" } + : { "anthropic-version": "2023-06-01" }; + case "openai": + return key ? { Authorization: `Bearer ${key}` } : {}; + case "gemini": { + const effectiveKey = key || process.env.GEMINI_API_KEY || ""; + return effectiveKey ? { "x-goog-api-key": effectiveKey } : {}; + } + case "custom": + return {}; + } +} + +export default async function handler( + req: VercelRequest, + res: VercelResponse, +) { + const allowedOrigin = getAllowedOrigin(req); + + if (req.method === "OPTIONS") { + setCorsHeaders(res, allowedOrigin); + res.status(204).end(); + return; + } + if (req.method !== "POST") { + res.status(405).json({ error: "method_not_allowed" }); + return; + } + + if (PROXY_SECRET) { + if (!hasValidSecret(req)) { + res.status(403).json({ error: "forbidden" }); + return; + } + } else { + const origin = req.headers.origin; + if (origin && !allowedOrigin) { + res.status(403).json({ error: "origin_not_allowed" }); + return; + } + } + + const guard = checkRequestGuards(req, readGuardConfigFromEnv()); + if (!guard.ok) { + res.status(guard.status ?? 403).json({ error: guard.reason ?? "forbidden" }); + return; + } + + const body = (req.body ?? {}) as InvokeBody; + if (!body.provider || !body.path || !body.body) { + res.status(400).json({ error: "missing provider/path/body" }); + return; + } + + if (body.provider === "custom") { + res.status(400).json({ + error: + "custom provider endpoints must be called browser-direct with the user key; server proxy allowlists known providers only", + }); + return; + } + + if (!isAllowedProviderUrl(body.provider, body.path)) { + res.status(400).json({ error: `path not allowed for provider ${body.provider}` }); + return; + } + + const serialized = JSON.stringify(body.body); + if (serialized.length > MAX_BODY_BYTES) { + res.status(413).json({ error: "request_body_too_large" }); + return; + } + + let upstreamUrl: string; + try { + upstreamUrl = resolveProviderUrl(body.provider, body.path); + } catch (err) { + res.status(400).json({ error: (err as Error).message }); + return; + } + + const userKey = (req.headers["x-user-api-key"] as string | undefined) || undefined; + const authHeaders = providerAuthHeaders(body.provider, userKey); + + try { + const upstream = await fetch(upstreamUrl, { + method: "POST", + headers: { + "content-type": "application/json", + ...authHeaders, + }, + body: serialized, + signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS), + }); + + const contentType = upstream.headers.get("content-type") ?? "application/json"; + const isStream = contentType.includes("event-stream"); + + res.status(upstream.status); + res.setHeader("content-type", contentType); + setCorsHeaders(res, allowedOrigin); + if (isStream) { + res.setHeader("cache-control", "no-cache, no-transform"); + res.setHeader("connection", "keep-alive"); + } + + if (!upstream.body) { + res.end(); + return; + } + + const reader = upstream.body.getReader(); + const flushable = res as unknown as { flush?: () => void }; + try { + while (true) { + const { value, done } = await reader.read(); + if (done) break; + if (value && value.byteLength > 0) { + res.write(Buffer.from(value)); + if (isStream && typeof flushable.flush === "function") flushable.flush(); + } + } + } finally { + reader.releaseLock(); + } + res.end(); + } catch (err) { + if (!res.headersSent) { + res.status(502).json({ error: "upstream_failed", detail: (err as Error).message }); + } else { + res.end(); + } + } +} diff --git a/edb b/edb index 1ba2fca..c5b32ca 160000 --- a/edb +++ b/edb @@ -1 +1 @@ -Subproject commit 1ba2fcaca73cee96bf10107b7b0f98ab1ceab1a4 +Subproject commit c5b32ca53e7c2146e647830af486b6481aa37548 diff --git a/fhe/.gitignore b/fhe/.gitignore new file mode 100644 index 0000000..2b55766 --- /dev/null +++ b/fhe/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +.env +artifacts/ +cache/ +typechain-types/ +dist/ +coverage/ +coverage.json +deployments/localcofhe/ diff --git a/fhe/README.md b/fhe/README.md new file mode 100644 index 0000000..90aea43 --- /dev/null +++ b/fhe/README.md @@ -0,0 +1,36 @@ +# `fhe/` — TxCaptain CoFHE toolchain + +Isolated Hardhat workspace for the encrypted-triage contracts (`HackTriage`, `RiskThrottle`). Runs against **real Ethereum Sepolia** — no mocks. + +## One-time setup + +```bash +cd fhe +pnpm install +cp .env.example .env # already created; fill in the values below +``` + +Edit `fhe/.env`: + +- `DEPLOYER_PRIVATE_KEY` — 0x-prefixed private key of a wallet funded with Ethereum Sepolia ETH. +- `ETH_SEPOLIA_RPC_URL` — optional custom RPC (Alchemy / Infura / QuickNode). Defaults to `https://ethereum-sepolia.publicnode.com`, which is rate-limited. +- `ETHERSCAN_API_KEY` — optional, only needed for `hardhat verify`. + +## Common commands + +```bash +pnpm compile # hardhat compile → artifacts/ +pnpm test # hardhat test --network sepolia (real testnet txs) +pnpm deploy:triage # deploy HackTriage, write address to deployments/sepolia.json +pnpm deploy:throttle # deploy RiskThrottle (reads HackTriage address from deployments) +``` + +## Why real testnet (no mocks) + +CoFHE encrypted-value semantics only run end-to-end against the live coprocessor on Ethereum Sepolia. Local mocks (`@cofhe/mock-contracts`) don't exercise the permit system or async decryption timing. The Wave 3 submission requires a deployed demo anyway, so we bake that into the test loop. + +Each `pnpm test` run costs a few cents of Sepolia ETH per contract deployment + triage call. + +## Artifacts → Vite app + +Compiled ABIs land in `fhe/artifacts/contracts/.sol/.json`. The Vite app imports them from there (Task 33e wires this up explicitly). diff --git a/fhe/contracts/HackTriage.sol b/fhe/contracts/HackTriage.sol new file mode 100644 index 0000000..9a36009 --- /dev/null +++ b/fhe/contracts/HackTriage.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { FHE, InEuint16, euint16, euint8, ebool } from "@fhenixprotocol/cofhe-contracts/FHE.sol"; + +contract HackTriage { + // Feature bits (mirrors FEATURE_BITS in TS). + uint16 private constant HAS_FLASH_SEL = 1 << 0; + uint16 private constant HAS_TRANSFER_FROM_EXT = 1 << 1; + uint16 private constant HAS_DELEGATECALL = 1 << 2; + uint16 private constant HAS_SLOT0_WRITE = 1 << 3; + uint16 private constant TARGET_UNVERIFIED = 1 << 4; + uint16 private constant TARGET_IS_SAFE = 1 << 5; + uint16 private constant ATTACKER_PROFIT_IN = 1 << 6; + uint16 private constant ATTACKER_PROFIT_LARGE = 1 << 7; + uint16 private constant REPEATED_WRITES_MAX3 = 1 << 8; + uint16 private constant PRIVILEGED_WRITE_LABEL = 1 << 9; + + uint16 private constant MASK_FLASH_PRICE = HAS_FLASH_SEL | REPEATED_WRITES_MAX3 | ATTACKER_PROFIT_LARGE; + uint16 private constant MASK_DELEGATE_USER = HAS_DELEGATECALL | HAS_SLOT0_WRITE | TARGET_UNVERIFIED; + uint16 private constant MASK_APPROVAL_DRAIN = HAS_TRANSFER_FROM_EXT | ATTACKER_PROFIT_IN; + uint16 private constant MASK_SIGNER_COMP = HAS_DELEGATECALL | HAS_SLOT0_WRITE | TARGET_IS_SAFE; + uint16 private constant MASK_ACCESS_CTRL = PRIVILEGED_WRITE_LABEL | ATTACKER_PROFIT_IN; + + uint16 private constant CLASS_FLASH = 1 << 0; + uint16 private constant CLASS_DELEGATE = 1 << 1; + uint16 private constant CLASS_APPROVAL = 1 << 2; + uint16 private constant CLASS_SIGNER = 1 << 3; + uint16 private constant CLASS_ACCESS = 1 << 4; + + struct Verdict { euint16 classBits; euint8 severity; uint64 blockNumber; } + mapping(address => Verdict) public latest; + + event TriageSubmitted(address indexed caller, uint64 blockNumber); + + function triage(InEuint16 calldata encFeatures) external returns (euint16 classBits, euint8 severity) { + euint16 features = FHE.asEuint16(encFeatures); + + classBits = _ruleFire(features, MASK_FLASH_PRICE, CLASS_FLASH); + classBits = FHE.or(classBits, _ruleFire(features, MASK_DELEGATE_USER, CLASS_DELEGATE)); + classBits = FHE.or(classBits, _ruleFire(features, MASK_APPROVAL_DRAIN, CLASS_APPROVAL)); + classBits = FHE.or(classBits, _ruleFire(features, MASK_SIGNER_COMP, CLASS_SIGNER)); + classBits = FHE.or(classBits, _ruleFire(features, MASK_ACCESS_CTRL, CLASS_ACCESS)); + + euint8 sev = FHE.asEuint8(uint256(0)); + sev = FHE.add(sev, _weight(features, MASK_FLASH_PRICE, 2)); + sev = FHE.add(sev, _weight(features, MASK_DELEGATE_USER, 2)); + sev = FHE.add(sev, _weight(features, MASK_APPROVAL_DRAIN, 2)); + sev = FHE.add(sev, _weight(features, MASK_SIGNER_COMP, 2)); + sev = FHE.add(sev, _weight(features, MASK_ACCESS_CTRL, 1)); + severity = sev; + + latest[msg.sender] = Verdict({ classBits: classBits, severity: severity, blockNumber: uint64(block.number) }); + + FHE.allowThis(classBits); + FHE.allowThis(severity); + FHE.allowSender(classBits); + FHE.allowSender(severity); + + emit TriageSubmitted(msg.sender, uint64(block.number)); + } + + /// Grants ACL permission for `consumer` to read and operate on the caller's latest + /// verdict ciphertexts. Must be called by the same address that submitted the triage() + /// (so `latest[msg.sender]` refers to their verdict). + function allowConsumer(address consumer) external { + Verdict storage v = latest[msg.sender]; + FHE.allow(v.classBits, consumer); + FHE.allow(v.severity, consumer); + } + + function _ruleFire(euint16 features, uint16 mask, uint16 classBit) internal returns (euint16) { + ebool fired = FHE.eq(FHE.and(features, FHE.asEuint16(uint256(mask))), FHE.asEuint16(uint256(mask))); + return FHE.select(fired, FHE.asEuint16(uint256(classBit)), FHE.asEuint16(uint256(0))); + } + + function _weight(euint16 features, uint16 mask, uint8 w) internal returns (euint8) { + ebool fired = FHE.eq(FHE.and(features, FHE.asEuint16(uint256(mask))), FHE.asEuint16(uint256(mask))); + return FHE.select(fired, FHE.asEuint8(uint256(w)), FHE.asEuint8(uint256(0))); + } +} diff --git a/fhe/contracts/RiskThrottle.sol b/fhe/contracts/RiskThrottle.sol new file mode 100644 index 0000000..4b130dd --- /dev/null +++ b/fhe/contracts/RiskThrottle.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { FHE, euint8, ebool } from "@fhenixprotocol/cofhe-contracts/FHE.sol"; +import { HackTriage } from "./HackTriage.sol"; + +contract RiskThrottle { + uint8 private constant STATUS_NORMAL = 0; + uint8 private constant STATUS_QUARANTINE = 1; + uint8 private constant STATUS_ESCALATE = 2; + + HackTriage public immutable triage; + + /// Encrypted throttle status, scoped per-caller. Each address gets its own encrypted + /// status derived from its own HackTriage verdict. + mapping(address => euint8) public status; + + event StatusRefreshed(address indexed caller, uint64 blockNumber); + + constructor(address _triage) { + triage = HackTriage(_triage); + } + + /// Reads the caller's latest severity from HackTriage, maps it through the FHE.select + /// status ladder, stores the encrypted result in status[msg.sender], and grants the + /// caller decrypt ACL on their own status ciphertext. + /// Thresholds: severity >= 4 -> ESCALATE, >= 2 -> QUARANTINE, else NORMAL. + function refresh() external { + euint8 sev = _readSeverity(msg.sender); + + ebool gte4 = FHE.gte(sev, FHE.asEuint8(uint256(4))); + ebool gte2 = FHE.gte(sev, FHE.asEuint8(uint256(2))); + + euint8 lvl2 = FHE.select(gte2, FHE.asEuint8(uint256(STATUS_QUARANTINE)), FHE.asEuint8(uint256(STATUS_NORMAL))); + euint8 next = FHE.select(gte4, FHE.asEuint8(uint256(STATUS_ESCALATE)), lvl2); + + status[msg.sender] = next; + FHE.allowThis(next); + FHE.allowSender(next); + + emit StatusRefreshed(msg.sender, uint64(block.number)); + } + + function _readSeverity(address p) internal view returns (euint8) { + (, euint8 sev, ) = triage.latest(p); + return sev; + } +} diff --git a/fhe/deployments/sepolia.json b/fhe/deployments/sepolia.json new file mode 100644 index 0000000..1bc656d --- /dev/null +++ b/fhe/deployments/sepolia.json @@ -0,0 +1,8 @@ +{ + "HackTriage": "0xBe02be322c7733759ee068067BD620791e9e73D4", + "block": 10694395, + "deployer": "0x2fdB4B2288137025476c9B5E135b0F1738472dB6", + "network": "sepolia", + "chainId": 11155111, + "timestamp": "2026-04-20T02:16:02.151Z" +} \ No newline at end of file diff --git a/fhe/deployments/throttle-sepolia.json b/fhe/deployments/throttle-sepolia.json new file mode 100644 index 0000000..77f4b25 --- /dev/null +++ b/fhe/deployments/throttle-sepolia.json @@ -0,0 +1,9 @@ +{ + "RiskThrottle": "0x1FbB01D8e3FE4E99D6742eeBf1A4e276050ecD05", + "HackTriage": "0xBe02be322c7733759ee068067BD620791e9e73D4", + "block": 10694623, + "deployer": "0x2fdB4B2288137025476c9B5E135b0F1738472dB6", + "network": "sepolia", + "chainId": 11155111, + "timestamp": "2026-04-20T03:10:01.397Z" +} \ No newline at end of file diff --git a/fhe/hardhat.config.ts b/fhe/hardhat.config.ts new file mode 100644 index 0000000..866c322 --- /dev/null +++ b/fhe/hardhat.config.ts @@ -0,0 +1,48 @@ +import { config as loadEnv } from "dotenv"; +import "@nomicfoundation/hardhat-toolbox"; +import "@nomicfoundation/hardhat-ethers"; +import "@cofhe/hardhat-plugin"; +import type { HardhatUserConfig } from "hardhat/config"; + +loadEnv({ path: `${__dirname}/.env` }); + +const DEPLOYER_PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY; +const ETH_SEPOLIA_RPC_URL = process.env.ETH_SEPOLIA_RPC_URL ?? "https://ethereum-sepolia.publicnode.com"; +const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY ?? ""; + +const accounts = DEPLOYER_PRIVATE_KEY ? [DEPLOYER_PRIVATE_KEY] : []; + +const config: HardhatUserConfig = { + solidity: { + compilers: [ + { + version: "0.8.25", + settings: { + optimizer: { enabled: true, runs: 200 }, + viaIR: true, + }, + }, + ], + }, + networks: { + sepolia: { + url: ETH_SEPOLIA_RPC_URL, + chainId: 11155111, + accounts, + gasMultiplier: 1.2, + timeout: 60000, + }, + }, + etherscan: { + apiKey: ETHERSCAN_API_KEY, + }, + paths: { + sources: "./contracts", + tests: "./test", + cache: "./cache", + artifacts: "./artifacts", + }, + mocha: { timeout: 600000 }, +}; + +export default config; diff --git a/fhe/package.json b/fhe/package.json new file mode 100644 index 0000000..29f377d --- /dev/null +++ b/fhe/package.json @@ -0,0 +1,37 @@ +{ + "name": "hack-triage-fhe", + "version": "0.1.0", + "private": true, + "description": "Fhenix CoFHE Solidity toolchain for TxCaptain Wave 3 (HackTriage + RiskThrottle). Sibling to the Vite app; isolated node_modules.", + "scripts": { + "compile": "hardhat compile", + "clean": "rimraf ./artifacts ./cache ./typechain-types", + "test": "hardhat test --network sepolia", + "deploy:triage": "hardhat run scripts/deployHackTriage.ts --network sepolia", + "deploy:throttle": "hardhat run scripts/deployRiskThrottle.ts --network sepolia" + }, + "devDependencies": { + "@cofhe/hardhat-plugin": "^0.4.0", + "@cofhe/sdk": "^0.4.0", + "@fhenixprotocol/cofhe-contracts": "^0.1.3", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-toolbox": "^5.0.0", + "@nomicfoundation/hardhat-verify": "^2.0.0", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "^9.0.0", + "@types/chai": "^4.2.0", + "@types/mocha": ">=9.1.0", + "@types/node": ">=18.0.0", + "chai": "^4.2.0", + "dotenv": "^16.4.7", + "ethers": "^6.4.0", + "hardhat": "^2.22.19", + "hardhat-gas-reporter": "^1.0.8", + "rimraf": "^5.0.0", + "ts-node": ">=8.0.0", + "typechain": "^8.3.0", + "typescript": ">=4.5.0" + } +} diff --git a/fhe/pnpm-lock.yaml b/fhe/pnpm-lock.yaml new file mode 100644 index 0000000..6191056 --- /dev/null +++ b/fhe/pnpm-lock.yaml @@ -0,0 +1,5136 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@cofhe/hardhat-plugin': + specifier: ^0.4.0 + version: 0.4.0(@fhenixprotocol/cofhe-contracts@0.1.3)(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(@openzeppelin/contracts@5.6.1)(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(zod@4.3.6) + '@cofhe/sdk': + specifier: ^0.4.0 + version: 0.4.0(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(viem@2.48.1(typescript@6.0.3)(zod@4.3.6)) + '@fhenixprotocol/cofhe-contracts': + specifier: ^0.1.3 + version: 0.1.3 + '@nomicfoundation/hardhat-chai-matchers': + specifier: ^2.0.0 + version: 2.1.2(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(chai@4.5.0)(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.0 + version: 3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-network-helpers': + specifier: ^1.0.0 + version: 1.1.2(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-toolbox': + specifier: ^5.0.0 + version: 5.0.0(14ff62d0485f76ac538cea781f58ea70) + '@nomicfoundation/hardhat-verify': + specifier: ^2.0.0 + version: 2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@typechain/ethers-v6': + specifier: ^0.5.0 + version: 0.5.1(ethers@6.16.0)(typechain@8.3.2(typescript@6.0.3))(typescript@6.0.3) + '@typechain/hardhat': + specifier: ^9.0.0 + version: 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.16.0)(typechain@8.3.2(typescript@6.0.3))(typescript@6.0.3))(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(typechain@8.3.2(typescript@6.0.3)) + '@types/chai': + specifier: ^4.2.0 + version: 4.3.20 + '@types/mocha': + specifier: '>=9.1.0' + version: 10.0.10 + '@types/node': + specifier: '>=18.0.0' + version: 25.6.0 + chai: + specifier: ^4.2.0 + version: 4.5.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + ethers: + specifier: ^6.4.0 + version: 6.16.0 + hardhat: + specifier: ^2.22.19 + version: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + hardhat-gas-reporter: + specifier: ^1.0.8 + version: 1.0.10(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + rimraf: + specifier: ^5.0.0 + version: 5.0.10 + ts-node: + specifier: '>=8.0.0' + version: 10.9.2(@types/node@25.6.0)(typescript@6.0.3) + typechain: + specifier: ^8.3.0 + version: 8.3.2(typescript@6.0.3) + typescript: + specifier: '>=4.5.0' + version: 6.0.3 + +packages: + + '@adraffy/ens-normalize@1.10.1': + resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} + + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} + + '@cofhe/hardhat-plugin@0.4.0': + resolution: {integrity: sha512-5niMZUWKadSuXyUV5H6p4t9WnQYbcdoSiHE4LCUBP2GHcmo98slaersIuf5GbRvhUxMXSKGDjMqH7x6Zwtb4+A==} + peerDependencies: + '@fhenixprotocol/cofhe-contracts': '>=0.1.0' + '@nomicfoundation/hardhat-ethers': ^3.0.0 + '@openzeppelin/contracts': ^5.0.0 + ethers: ^5.0.0 || ^6.0.0 + hardhat: ^2.0.0 + + '@cofhe/mock-contracts@0.4.0': + resolution: {integrity: sha512-yj/8d7fg7GhHVl/gjz8CYlRW7roGm2WIRCwp68vQZA7iaBTGOPzpvg15IRrmIUapJRka2pQLR7luYfUXYnsTYQ==} + + '@cofhe/sdk@0.4.0': + resolution: {integrity: sha512-L+X8S9sKV0pT7gMAx4OxluiKnjP3uPQVw1GN+HBE8lRo8icZcPyGU2LrZQoXvnTU04g+147zvBhoZ0cSCMj1/Q==} + peerDependencies: + '@nomicfoundation/hardhat-ethers': ^3.0.0 + '@wagmi/core': ^2.0.0 + ethers: ^5.0.0 || ^6.0.0 + hardhat: ^2.0.0 + viem: ^2.38.6 + peerDependenciesMeta: + '@nomicfoundation/hardhat-ethers': + optional: true + '@wagmi/core': + optional: true + ethers: + optional: true + hardhat: + optional: true + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@ethereumjs/rlp@4.0.1': + resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} + engines: {node: '>=14'} + hasBin: true + + '@ethereumjs/rlp@5.0.2': + resolution: {integrity: sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==} + engines: {node: '>=18'} + hasBin: true + + '@ethereumjs/util@8.1.0': + resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} + engines: {node: '>=14'} + + '@ethereumjs/util@9.1.0': + resolution: {integrity: sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==} + engines: {node: '>=18'} + + '@ethersproject/abi@5.8.0': + resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} + + '@ethersproject/abstract-provider@5.8.0': + resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} + + '@ethersproject/abstract-signer@5.8.0': + resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} + + '@ethersproject/address@5.6.1': + resolution: {integrity: sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==} + + '@ethersproject/address@5.8.0': + resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} + + '@ethersproject/base64@5.8.0': + resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} + + '@ethersproject/basex@5.8.0': + resolution: {integrity: sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==} + + '@ethersproject/bignumber@5.8.0': + resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} + + '@ethersproject/bytes@5.8.0': + resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} + + '@ethersproject/constants@5.8.0': + resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} + + '@ethersproject/contracts@5.8.0': + resolution: {integrity: sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==} + + '@ethersproject/hash@5.8.0': + resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} + + '@ethersproject/hdnode@5.8.0': + resolution: {integrity: sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==} + + '@ethersproject/json-wallets@5.8.0': + resolution: {integrity: sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==} + + '@ethersproject/keccak256@5.8.0': + resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} + + '@ethersproject/logger@5.8.0': + resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} + + '@ethersproject/networks@5.8.0': + resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} + + '@ethersproject/pbkdf2@5.8.0': + resolution: {integrity: sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==} + + '@ethersproject/properties@5.8.0': + resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} + + '@ethersproject/providers@5.8.0': + resolution: {integrity: sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==} + + '@ethersproject/random@5.8.0': + resolution: {integrity: sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==} + + '@ethersproject/rlp@5.8.0': + resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} + + '@ethersproject/sha2@5.8.0': + resolution: {integrity: sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==} + + '@ethersproject/signing-key@5.8.0': + resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} + + '@ethersproject/solidity@5.8.0': + resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} + + '@ethersproject/strings@5.8.0': + resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} + + '@ethersproject/transactions@5.8.0': + resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} + + '@ethersproject/units@5.8.0': + resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} + + '@ethersproject/wallet@5.8.0': + resolution: {integrity: sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==} + + '@ethersproject/web@5.8.0': + resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} + + '@ethersproject/wordlists@5.8.0': + resolution: {integrity: sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==} + + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + + '@fhenixprotocol/cofhe-contracts@0.1.0': + resolution: {integrity: sha512-Nt5+bJtRgiT9lUrBXEEhJLttVtqO/Rk7wI5KC5+la1zGcYnCym/mT+ExlyjsX+Pf9cpE2Qje8nhfTDSMERGLtA==} + + '@fhenixprotocol/cofhe-contracts@0.1.3': + resolution: {integrity: sha512-VegEHsy3a6DMHGlzhw5Xbqhp7zYvlK601z5VO5qQeeBjmOXEVhyWvVrpvmJgZfpDoT+bdCL19qExBJzKH5aYvA==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.2.0': + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + + '@noble/curves@1.4.2': + resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} + + '@noble/curves@1.8.2': + resolution: {integrity: sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.2.0': + resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} + + '@noble/hashes@1.3.2': + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + + '@noble/hashes@1.7.2': + resolution: {integrity: sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@noble/secp256k1@1.7.1': + resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nomicfoundation/edr-darwin-arm64@0.12.0-next.23': + resolution: {integrity: sha512-Amh7mRoDzZyJJ4efqoePqdoZOzharmSOttZuJDlVE5yy07BoE8hL6ZRpa5fNYn0LCqn/KoWs8OHANWxhKDGhvQ==} + engines: {node: '>= 20'} + + '@nomicfoundation/edr-darwin-x64@0.12.0-next.23': + resolution: {integrity: sha512-9wn489FIQm7m0UCD+HhktjWx6vskZzeZD9oDc2k9ZvbBzdXwPp5tiDqUBJ+eQpByAzCDfteAJwRn2lQCE0U+Iw==} + engines: {node: '>= 20'} + + '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.23': + resolution: {integrity: sha512-nlk5EejSzEUfEngv0Jkhqq3/wINIfF2ED9wAofc22w/V1DV99ASh9l3/e/MIHOQFecIZ9MDqt0Em9/oDyB1Uew==} + engines: {node: '>= 20'} + + '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.23': + resolution: {integrity: sha512-SJuPBp3Rc6vM92UtVTUxZQ/QlLhLfwTftt2XUiYohmGKB3RjGzpgduEFMCA0LEnucUckU6UHrJNFHiDm77C4PQ==} + engines: {node: '>= 20'} + + '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.23': + resolution: {integrity: sha512-NU+Qs3u7Qt6t3bJFdmmjd5CsvgI2bPPzO31KifM2Ez96/jsXYho5debtTQnimlb5NAqiHTSlxjh/F8ROcptmeQ==} + engines: {node: '>= 20'} + + '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.23': + resolution: {integrity: sha512-F78fZA2h6/ssiCSZOovlgIu0dUeI7ItKPsDDF3UUlIibef052GCXmliMinC90jVPbrjUADMd1BUwjfI0Z8OllQ==} + engines: {node: '>= 20'} + + '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.23': + resolution: {integrity: sha512-IfJZQJn7d/YyqhmguBIGoCKjE9dKjbu6V6iNEPApfwf5JyyjHYyyfkLU4rf7hygj57bfH4sl1jtQ6r8HnT62lw==} + engines: {node: '>= 20'} + + '@nomicfoundation/edr@0.12.0-next.23': + resolution: {integrity: sha512-F2/6HZh8Q9RsgkOIkRrckldbhPjIZY7d4mT9LYuW68miwGQ5l7CkAgcz9fRRiurA0+YJhtsbx/EyrD9DmX9BOw==} + engines: {node: '>= 20'} + + '@nomicfoundation/hardhat-chai-matchers@2.1.2': + resolution: {integrity: sha512-NlUlde/ycXw2bLzA2gWjjbxQaD9xIRbAF30nsoEprAWzH8dXEI1ILZUKZMyux9n9iygEXTzN0SDVjE6zWDZi9g==} + peerDependencies: + '@nomicfoundation/hardhat-ethers': ^3.1.0 + chai: ^4.2.0 + ethers: ^6.14.0 + hardhat: ^2.26.0 + + '@nomicfoundation/hardhat-ethers@3.1.3': + resolution: {integrity: sha512-208JcDeVIl+7Wu3MhFUUtiA8TJ7r2Rn3Wr+lSx9PfsDTKkbsAsWPY6N6wQ4mtzDv0/pB9nIbJhkjoHe1EsgNsA==} + peerDependencies: + ethers: ^6.14.0 + hardhat: ^2.28.0 + + '@nomicfoundation/hardhat-ignition-ethers@0.15.17': + resolution: {integrity: sha512-io6Wrp1dUsJ94xEI3pw6qkPfhc9TFA+e6/+o16yQ8pvBTFMjgK5x8wIHKrrIHr9L3bkuTMtmDjyN4doqO2IqFQ==} + peerDependencies: + '@nomicfoundation/hardhat-ethers': ^3.1.0 + '@nomicfoundation/hardhat-ignition': ^0.15.16 + '@nomicfoundation/ignition-core': ^0.15.15 + ethers: ^6.14.0 + hardhat: ^2.26.0 + + '@nomicfoundation/hardhat-ignition@0.15.16': + resolution: {integrity: sha512-T0JTnuib7QcpsWkHCPLT7Z6F483EjTdcdjb1e00jqS9zTGCPqinPB66LLtR/duDLdvgoiCVS6K8WxTQkA/xR1Q==} + peerDependencies: + '@nomicfoundation/hardhat-verify': ^2.1.0 + hardhat: ^2.26.0 + + '@nomicfoundation/hardhat-network-helpers@1.1.2': + resolution: {integrity: sha512-p7HaUVDbLj7ikFivQVNhnfMHUBgiHYMwQWvGn9AriieuopGOELIrwj2KjyM2a6z70zai5YKO264Vwz+3UFJZPQ==} + peerDependencies: + hardhat: ^2.26.0 + + '@nomicfoundation/hardhat-toolbox@5.0.0': + resolution: {integrity: sha512-FnUtUC5PsakCbwiVNsqlXVIWG5JIb5CEZoSXbJUsEBun22Bivx2jhF1/q9iQbzuaGpJKFQyOhemPB2+XlEE6pQ==} + peerDependencies: + '@nomicfoundation/hardhat-chai-matchers': ^2.0.0 + '@nomicfoundation/hardhat-ethers': ^3.0.0 + '@nomicfoundation/hardhat-ignition-ethers': ^0.15.0 + '@nomicfoundation/hardhat-network-helpers': ^1.0.0 + '@nomicfoundation/hardhat-verify': ^2.0.0 + '@typechain/ethers-v6': ^0.5.0 + '@typechain/hardhat': ^9.0.0 + '@types/chai': ^4.2.0 + '@types/mocha': '>=9.1.0' + '@types/node': '>=18.0.0' + chai: ^4.2.0 + ethers: ^6.4.0 + hardhat: ^2.11.0 + hardhat-gas-reporter: ^1.0.8 + solidity-coverage: ^0.8.1 + ts-node: '>=8.0.0' + typechain: ^8.3.0 + typescript: '>=4.5.0' + + '@nomicfoundation/hardhat-verify@2.1.3': + resolution: {integrity: sha512-danbGjPp2WBhLkJdQy9/ARM3WQIK+7vwzE0urNem1qZJjh9f54Kf5f1xuQv8DvqewUAkuPxVt/7q4Grz5WjqSg==} + peerDependencies: + hardhat: ^2.26.0 + + '@nomicfoundation/ignition-core@0.15.15': + resolution: {integrity: sha512-JdKFxYknTfOYtFXMN6iFJ1vALJPednuB+9p9OwGIRdoI6HYSh4ZBzyRURgyXtHFyaJ/SF9lBpsYV9/1zEpcYwg==} + + '@nomicfoundation/ignition-ui@0.15.13': + resolution: {integrity: sha512-HbTszdN1iDHCkUS9hLeooqnLEW2U45FaqFwFEYT8nIno2prFZhG+n68JEERjmfFCB5u0WgbuJwk3CgLoqtSL7Q==} + + '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.2': + resolution: {integrity: sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==} + engines: {node: '>= 12'} + + '@nomicfoundation/solidity-analyzer-darwin-x64@0.1.2': + resolution: {integrity: sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==} + engines: {node: '>= 12'} + + '@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.2': + resolution: {integrity: sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==} + engines: {node: '>= 12'} + + '@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.2': + resolution: {integrity: sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==} + engines: {node: '>= 12'} + + '@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.2': + resolution: {integrity: sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==} + engines: {node: '>= 12'} + + '@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.2': + resolution: {integrity: sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==} + engines: {node: '>= 12'} + + '@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.2': + resolution: {integrity: sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==} + engines: {node: '>= 12'} + + '@nomicfoundation/solidity-analyzer@0.1.2': + resolution: {integrity: sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==} + engines: {node: '>= 12'} + + '@openzeppelin/contracts-upgradeable@5.6.1': + resolution: {integrity: sha512-n4a/vfRs114lXyUdYg7pyY8LvFKWvCDF5lEcRRAVxap8g6ZEdLqm+9tmt2zTtRHcNMxTYp9y5t6KBof4tHp7Og==} + peerDependencies: + '@openzeppelin/contracts': 5.6.1 + + '@openzeppelin/contracts@5.6.1': + resolution: {integrity: sha512-Ly6SlsVJ3mj+b18W3R8gNufB7dTICT105fJhodGAGgyC2oqnBAhqSiNDJ8V8DLY05cCz81GLI0CU5vNYA1EC/w==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + + '@scure/bip32@1.1.5': + resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} + + '@scure/bip32@1.4.0': + resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.1.1': + resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} + + '@scure/bip39@1.3.0': + resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + + '@sentry/core@5.30.0': + resolution: {integrity: sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==} + engines: {node: '>=6'} + + '@sentry/hub@5.30.0': + resolution: {integrity: sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==} + engines: {node: '>=6'} + + '@sentry/minimal@5.30.0': + resolution: {integrity: sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==} + engines: {node: '>=6'} + + '@sentry/node@5.30.0': + resolution: {integrity: sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==} + engines: {node: '>=6'} + + '@sentry/tracing@5.30.0': + resolution: {integrity: sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==} + engines: {node: '>=6'} + + '@sentry/types@5.30.0': + resolution: {integrity: sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==} + engines: {node: '>=6'} + + '@sentry/utils@5.30.0': + resolution: {integrity: sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==} + engines: {node: '>=6'} + + '@solidity-parser/parser@0.14.5': + resolution: {integrity: sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==} + + '@solidity-parser/parser@0.20.2': + resolution: {integrity: sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@typechain/ethers-v6@0.5.1': + resolution: {integrity: sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==} + peerDependencies: + ethers: 6.x + typechain: ^8.3.2 + typescript: '>=4.7.0' + + '@typechain/hardhat@9.1.0': + resolution: {integrity: sha512-mtaUlzLlkqTlfPwB3FORdejqBskSnh+Jl8AIJGjXNAQfRQ4ofHADPl1+oU7Z3pAJzmZbUXII8MhOLQltcHgKnA==} + peerDependencies: + '@typechain/ethers-v6': ^0.5.1 + ethers: ^6.1.0 + hardhat: ^2.9.9 + typechain: ^8.3.2 + + '@types/bn.js@5.2.0': + resolution: {integrity: sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==} + + '@types/chai-as-promised@7.1.8': + resolution: {integrity: sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==} + + '@types/chai@4.3.20': + resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} + + '@types/concat-stream@1.6.1': + resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} + + '@types/form-data@0.0.33': + resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} + + '@types/glob@7.2.0': + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + + '@types/minimatch@6.0.0': + resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} + deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. + + '@types/mocha@10.0.10': + resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} + + '@types/node@10.17.60': + resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} + + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + + '@types/node@8.10.66': + resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} + + '@types/pbkdf2@3.1.2': + resolution: {integrity: sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==} + + '@types/prettier@2.7.3': + resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + + '@types/secp256k1@4.0.7': + resolution: {integrity: sha512-Rcvjl6vARGAKRO6jHeKMatGrvOMGrR/AR11N1x2LqintPCyDZ7NBhrh238Z2VZc7aM7KIwnFpFQ7fnfK4H/9Qw==} + + abbrev@1.0.9: + resolution: {integrity: sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==} + + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + adm-zip@0.4.16: + resolution: {integrity: sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==} + engines: {node: '>=0.3.0'} + + aes-js@3.0.0: + resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} + + aes-js@4.0.0-beta.5: + resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + amdefine@1.0.1: + resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} + engines: {node: '>=0.4.2'} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@3.0.1: + resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} + engines: {node: '>=4'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + antlr4ts@0.5.0-alpha.4: + resolution: {integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-back@3.1.0: + resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} + engines: {node: '>=6'} + + array-back@4.0.2: + resolution: {integrity: sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==} + engines: {node: '>=8'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array-uniq@1.0.3: + resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} + engines: {node: '>=0.10.0'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async@1.5.2: + resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.15.1: + resolution: {integrity: sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base-x@3.0.11: + resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} + + bech32@1.1.4: + resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + blakejs@1.2.1: + resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} + + bn.js@4.11.6: + resolution: {integrity: sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==} + + bn.js@4.12.3: + resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} + + bn.js@5.2.3: + resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} + + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + browserify-aes@1.2.0: + resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} + + bs58@4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + + bs58check@2.1.2: + resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer-xor@1.0.3: + resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + + cbor@8.1.0: + resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} + engines: {node: '>=12.19'} + + cbor@9.0.2: + resolution: {integrity: sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==} + engines: {node: '>=16'} + + chai-as-promised@7.1.2: + resolution: {integrity: sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==} + peerDependencies: + chai: '>= 2.1.2 < 6' + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + cipher-base@1.0.7: + resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} + engines: {node: '>= 0.10'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + + cli-table3@0.5.1: + resolution: {integrity: sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==} + engines: {node: '>=6'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + command-exists@1.2.9: + resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + + command-line-args@5.2.1: + resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} + engines: {node: '>=4.0.0'} + + command-line-usage@6.1.3: + resolution: {integrity: sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==} + engines: {node: '>=8.0.0'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + + cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + create-hash@1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} + + create-hmac@1.1.7: + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + + death@1.1.0: + resolution: {integrity: sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + + diff@5.2.2: + resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} + engines: {node: '>=0.3.1'} + + difflib@0.2.4: + resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@1.8.1: + resolution: {integrity: sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==} + engines: {node: '>=0.12.0'} + hasBin: true + + esprima@2.7.3: + resolution: {integrity: sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==} + engines: {node: '>=0.10.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@1.9.3: + resolution: {integrity: sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==} + engines: {node: '>=0.10.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eth-gas-reporter@0.2.27: + resolution: {integrity: sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==} + peerDependencies: + '@codechecks/client': ^0.1.0 + peerDependenciesMeta: + '@codechecks/client': + optional: true + + ethereum-bloom-filters@1.2.0: + resolution: {integrity: sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==} + + ethereum-cryptography@0.1.3: + resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} + + ethereum-cryptography@1.2.0: + resolution: {integrity: sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==} + + ethereum-cryptography@2.2.1: + resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + + ethereumjs-util@7.1.5: + resolution: {integrity: sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==} + engines: {node: '>=10.0.0'} + + ethers@5.8.0: + resolution: {integrity: sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==} + + ethers@6.16.0: + resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} + engines: {node: '>=14.0.0'} + + ethjs-unit@0.1.6: + resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} + engines: {node: '>=6.5.0', npm: '>=3'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + evp_bytestokey@1.0.3: + resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-replace@3.0.0: + resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} + engines: {node: '>=4.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@2.5.5: + resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} + engines: {node: '>= 0.12'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fp-ts@1.19.3: + resolution: {integrity: sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-readdir-recursive@1.1.0: + resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-port@3.2.0: + resolution: {integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==} + engines: {node: '>=4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + ghost-testrpc@0.0.2: + resolution: {integrity: sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@5.0.15: + resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + glob@7.1.7: + resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globby@10.0.2: + resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} + engines: {node: '>=8'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + hardhat-gas-reporter@1.0.10: + resolution: {integrity: sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA==} + peerDependencies: + hardhat: ^2.0.2 + + hardhat@2.28.6: + resolution: {integrity: sha512-zQze7qe+8ltwHvhX5NQ8sN1N37WWZGw8L63y+2XcPxGwAjc/SMF829z3NS6o1krX0sryhAsVBK/xrwUqlsot4Q==} + hasBin: true + peerDependencies: + ts-node: '*' + typescript: '*' + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + + has-flag@1.0.0: + resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} + engines: {node: '>=0.10.0'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hash-base@3.1.2: + resolution: {integrity: sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==} + engines: {node: '>= 0.8'} + + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + heap@0.2.7: + resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + + http-basic@8.1.3: + resolution: {integrity: sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==} + engines: {node: '>=6.0.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-response-object@3.0.2: + resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + idb-keyval@6.2.2: + resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} + + iframe-shared-storage@1.0.34: + resolution: {integrity: sha512-sb80NhdVgVNhkc72pTyAjcaHQNiIPeu314zFtXbQaTjp9OOnaZ5+IjS/ylNXs08kwvlmBpg7GG5GOWzjRMP9Ww==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immer@10.0.2: + resolution: {integrity: sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==} + + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immutable@4.3.8: + resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + + io-ts@1.10.4: + resolution: {integrity: sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hex-prefixed@1.0.0: + resolution: {integrity: sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==} + engines: {node: '>=6.5.0', npm: '>=3'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} + peerDependencies: + ws: '*' + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stream-stringify@3.1.6: + resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} + engines: {node: '>=7.10.1'} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsonschema@1.5.0: + resolution: {integrity: sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==} + + keccak@3.0.4: + resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} + engines: {node: '>=10.0.0'} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + levn@0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru_map@0.3.3: + resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + markdown-table@1.1.3: + resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + md5.js@1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micro-eth-signer@0.14.0: + resolution: {integrity: sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==} + + micro-ftch@0.3.1: + resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} + + micro-packed@0.7.3: + resolution: {integrity: sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mnemonist@0.38.5: + resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} + + mocha@10.8.2: + resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} + engines: {node: '>= 14.0.0'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + ndjson@2.0.0: + resolution: {integrity: sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==} + engines: {node: '>=10'} + hasBin: true + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-addon-api@2.0.2: + resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} + + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + node-tfhe@0.11.1: + resolution: {integrity: sha512-QFkIeIZ5IbdvhpkzImwakL2+sZFCTXCwGvOacE1NczR+zgrg5ixHU9KpjuuIi1YbbKFGJEMoSUmivDktuzFaKg==} + + nofilter@3.1.0: + resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} + engines: {node: '>=12.19'} + + nopt@3.0.6: + resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + number-to-bn@1.7.0: + resolution: {integrity: sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==} + engines: {node: '>=6.5.0', npm: '>=3'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + obliterator@2.0.5: + resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + + ordinal@1.0.3: + resolution: {integrity: sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + ox@0.14.17: + resolution: {integrity: sha512-jOzNb2Wlfzsr8z/GoCtd1bf6OSRuWuysvbhnHGD+7fV1WRbcBR6B0RYoe3xWnUedF7zp4l5APmS7CzAhUok/lA==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parse-cache-control@1.0.1: + resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + pbkdf2@3.1.5: + resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==} + engines: {node: '>= 0.10'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postmsg-rpc@2.4.0: + resolution: {integrity: sha512-adGH2zGSxhCUOfUfAXdRn4tgZVWauaSP2X8on+g7uBA45sxkzORL1oia95eXZtcZk5Sp4JTZmDFOTe+D24avBQ==} + + prelude-ls@1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + + recursive-readdir@2.2.3: + resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} + engines: {node: '>=6.0.0'} + + reduce-flatten@2.0.0: + resolution: {integrity: sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==} + engines: {node: '>=6'} + + req-cwd@2.0.0: + resolution: {integrity: sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==} + engines: {node: '>=4'} + + req-from@2.0.0: + resolution: {integrity: sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==} + engines: {node: '>=4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@3.0.0: + resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} + engines: {node: '>=4'} + + resolve@1.1.7: + resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} + + resolve@1.17.0: + resolution: {integrity: sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + + ripemd160@2.0.3: + resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} + engines: {node: '>= 0.8'} + + rlp@2.2.7: + resolution: {integrity: sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sc-istanbul@0.4.6: + resolution: {integrity: sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==} + hasBin: true + + scrypt-js@3.0.1: + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} + + secp256k1@4.0.4: + resolution: {integrity: sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==} + engines: {node: '>=18.0.0'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + + sha1@1.1.1: + resolution: {integrity: sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true + + shortid@2.2.17: + resolution: {integrity: sha512-GpbM3gLF1UUXZvQw6MCyulHkWbRseNO4cyBEZresZRorwl1+SLu1ZdqgVtuwqz8mB6RpwPkm541mYSqrKyJSaA==} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + solc@0.8.26: + resolution: {integrity: sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==} + engines: {node: '>=10.0.0'} + hasBin: true + + solidity-coverage@0.8.17: + resolution: {integrity: sha512-5P8vnB6qVX9tt1MfuONtCTEaEGO/O4WuEidPHIAJjx4sktHHKhO3rFvnE0q8L30nWJPTrcqGQMT7jpE29B2qow==} + hasBin: true + peerDependencies: + hardhat: ^2.11.0 + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.2.0: + resolution: {integrity: sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==} + engines: {node: '>=0.8.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string-format@2.0.0: + resolution: {integrity: sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==} + + string-width@2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@4.0.0: + resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} + engines: {node: '>=4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-hex-prefix@1.0.0: + resolution: {integrity: sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==} + engines: {node: '>=6.5.0', npm: '>=3'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@3.2.3: + resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} + engines: {node: '>=0.8.0'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + sync-request@6.1.0: + resolution: {integrity: sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==} + engines: {node: '>=8.0.0'} + + sync-rpc@1.3.6: + resolution: {integrity: sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==} + + table-layout@1.0.2: + resolution: {integrity: sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==} + engines: {node: '>=8.0.0'} + + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + + tfhe@0.11.1: + resolution: {integrity: sha512-D4YeL0DYz4IOV/X7YzKFexkjbJdOdGn2M7mfDNOQW1OxA+NO/Z61BJ6ULLTvdBEQ3N6P985lNO61Drgsjf0g/w==} + + then-request@6.0.2: + resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==} + engines: {node: '>=6.0.0'} + + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + ts-command-line-args@2.5.1: + resolution: {integrity: sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==} + hasBin: true + + ts-essentials@7.0.3: + resolution: {integrity: sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==} + peerDependencies: + typescript: '>=3.7.0' + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tsort@0.0.1: + resolution: {integrity: sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==} + + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + + type-check@0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + + typechain@8.3.2: + resolution: {integrity: sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==} + hasBin: true + peerDependencies: + typescript: '>=4.3.0' + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + typical@4.0.0: + resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} + engines: {node: '>=8'} + + typical@5.2.0: + resolution: {integrity: sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==} + engines: {node: '>=8'} + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + + undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} + engines: {node: '>=14.0'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + utf8@3.0.0: + resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + viem@2.48.1: + resolution: {integrity: sha512-GJC3gKV1Hngeo1IB9YanJKHH2pcmoqDymyPxddmzDtG8boXA7eFw8qdnn1PSaToJ93f3LpOZPlLLJ9beAF/Lzg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + web3-utils@1.10.4: + resolution: {integrity: sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==} + engines: {node: '>=8.0.0'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wordwrapjs@4.0.1: + resolution: {integrity: sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==} + engines: {node: '>=8.0.0'} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + 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 + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + 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 + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + 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 + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zustand@5.0.12: + resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@adraffy/ens-normalize@1.10.1': {} + + '@adraffy/ens-normalize@1.11.1': {} + + '@cofhe/hardhat-plugin@0.4.0(@fhenixprotocol/cofhe-contracts@0.1.3)(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(@openzeppelin/contracts@5.6.1)(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(typescript@6.0.3)(zod@4.3.6)': + dependencies: + '@cofhe/mock-contracts': 0.4.0 + '@cofhe/sdk': 0.4.0(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(viem@2.48.1(typescript@6.0.3)(zod@4.3.6)) + '@fhenixprotocol/cofhe-contracts': 0.1.3 + '@nomicfoundation/hardhat-ethers': 3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@openzeppelin/contracts': 5.6.1 + chai: 4.5.0 + chalk: 4.1.2 + ethers: 6.16.0 + fast-glob: 3.3.3 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + viem: 2.48.1(typescript@6.0.3)(zod@4.3.6) + transitivePeerDependencies: + - '@types/react' + - '@wagmi/core' + - bufferutil + - react + - typescript + - use-sync-external-store + - utf-8-validate + - zod + + '@cofhe/mock-contracts@0.4.0': + dependencies: + '@fhenixprotocol/cofhe-contracts': 0.1.0 + '@openzeppelin/contracts': 5.6.1 + '@openzeppelin/contracts-upgradeable': 5.6.1(@openzeppelin/contracts@5.6.1) + + '@cofhe/sdk@0.4.0(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(viem@2.48.1(typescript@6.0.3)(zod@4.3.6))': + dependencies: + iframe-shared-storage: 1.0.34 + immer: 10.2.0 + node-tfhe: 0.11.1 + tfhe: 0.11.1 + tweetnacl: 1.0.3 + viem: 2.48.1(typescript@6.0.3)(zod@4.3.6) + zod: 4.3.6 + zustand: 5.0.12(immer@10.2.0) + optionalDependencies: + '@nomicfoundation/hardhat-ethers': 3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + ethers: 6.16.0 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + transitivePeerDependencies: + - '@types/react' + - react + - use-sync-external-store + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@ethereumjs/rlp@4.0.1': {} + + '@ethereumjs/rlp@5.0.2': {} + + '@ethereumjs/util@8.1.0': + dependencies: + '@ethereumjs/rlp': 4.0.1 + ethereum-cryptography: 2.2.1 + micro-ftch: 0.3.1 + + '@ethereumjs/util@9.1.0': + dependencies: + '@ethereumjs/rlp': 5.0.2 + ethereum-cryptography: 2.2.1 + + '@ethersproject/abi@5.8.0': + dependencies: + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/abstract-provider@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + + '@ethersproject/abstract-signer@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + + '@ethersproject/address@5.6.1': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/rlp': 5.8.0 + + '@ethersproject/address@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/rlp': 5.8.0 + + '@ethersproject/base64@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + + '@ethersproject/basex@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/properties': 5.8.0 + + '@ethersproject/bignumber@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + bn.js: 5.2.3 + + '@ethersproject/bytes@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/constants@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + + '@ethersproject/contracts@5.8.0': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + + '@ethersproject/hash@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/hdnode@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + + '@ethersproject/json-wallets@5.8.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + + '@ethersproject/keccak256@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + js-sha3: 0.8.0 + + '@ethersproject/logger@5.8.0': {} + + '@ethersproject/networks@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/pbkdf2@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/sha2': 5.8.0 + + '@ethersproject/properties@5.8.0': + dependencies: + '@ethersproject/logger': 5.8.0 + + '@ethersproject/providers@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + bech32: 1.1.4 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@ethersproject/random@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/rlp@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/sha2@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + hash.js: 1.1.7 + + '@ethersproject/signing-key@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + bn.js: 5.2.3 + elliptic: 6.6.1 + hash.js: 1.1.7 + + '@ethersproject/solidity@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/strings@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/transactions@5.8.0': + dependencies: + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + + '@ethersproject/units@5.8.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + + '@ethersproject/wallet@5.8.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/json-wallets': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + + '@ethersproject/web@5.8.0': + dependencies: + '@ethersproject/base64': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/wordlists@5.8.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@fastify/busboy@2.1.1': {} + + '@fhenixprotocol/cofhe-contracts@0.1.0': + dependencies: + '@openzeppelin/contracts': 5.6.1 + + '@fhenixprotocol/cofhe-contracts@0.1.3': + dependencies: + '@openzeppelin/contracts': 5.6.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.2.0': + dependencies: + '@noble/hashes': 1.3.2 + + '@noble/curves@1.4.2': + dependencies: + '@noble/hashes': 1.4.0 + + '@noble/curves@1.8.2': + dependencies: + '@noble/hashes': 1.7.2 + + '@noble/curves@1.9.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.2.0': {} + + '@noble/hashes@1.3.2': {} + + '@noble/hashes@1.4.0': {} + + '@noble/hashes@1.7.2': {} + + '@noble/hashes@1.8.0': {} + + '@noble/secp256k1@1.7.1': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nomicfoundation/edr-darwin-arm64@0.12.0-next.23': {} + + '@nomicfoundation/edr-darwin-x64@0.12.0-next.23': {} + + '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.23': {} + + '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.23': {} + + '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.23': {} + + '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.23': {} + + '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.23': {} + + '@nomicfoundation/edr@0.12.0-next.23': + dependencies: + '@nomicfoundation/edr-darwin-arm64': 0.12.0-next.23 + '@nomicfoundation/edr-darwin-x64': 0.12.0-next.23 + '@nomicfoundation/edr-linux-arm64-gnu': 0.12.0-next.23 + '@nomicfoundation/edr-linux-arm64-musl': 0.12.0-next.23 + '@nomicfoundation/edr-linux-x64-gnu': 0.12.0-next.23 + '@nomicfoundation/edr-linux-x64-musl': 0.12.0-next.23 + '@nomicfoundation/edr-win32-x64-msvc': 0.12.0-next.23 + + '@nomicfoundation/hardhat-chai-matchers@2.1.2(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(chai@4.5.0)(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))': + dependencies: + '@nomicfoundation/hardhat-ethers': 3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@types/chai-as-promised': 7.1.8 + chai: 4.5.0 + chai-as-promised: 7.1.2(chai@4.5.0) + deep-eql: 4.1.4 + ethers: 6.16.0 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + ordinal: 1.0.3 + + '@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))': + dependencies: + debug: 4.4.3(supports-color@8.1.1) + ethers: 6.16.0 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + lodash.isequal: 4.5.0 + transitivePeerDependencies: + - supports-color + + '@nomicfoundation/hardhat-ignition-ethers@0.15.17(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(@nomicfoundation/hardhat-ignition@0.15.16(@nomicfoundation/hardhat-verify@2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(@nomicfoundation/ignition-core@0.15.15)(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))': + dependencies: + '@nomicfoundation/hardhat-ethers': 3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-ignition': 0.15.16(@nomicfoundation/hardhat-verify@2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/ignition-core': 0.15.15 + ethers: 6.16.0 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + + '@nomicfoundation/hardhat-ignition@0.15.16(@nomicfoundation/hardhat-verify@2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))': + dependencies: + '@nomicfoundation/hardhat-verify': 2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/ignition-core': 0.15.15 + '@nomicfoundation/ignition-ui': 0.15.13 + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + fs-extra: 10.1.0 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + json5: 2.2.3 + prompts: 2.4.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@nomicfoundation/hardhat-network-helpers@1.1.2(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))': + dependencies: + ethereumjs-util: 7.1.5 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + + '@nomicfoundation/hardhat-toolbox@5.0.0(14ff62d0485f76ac538cea781f58ea70)': + dependencies: + '@nomicfoundation/hardhat-chai-matchers': 2.1.2(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(chai@4.5.0)(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-ethers': 3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-ignition-ethers': 0.15.17(@nomicfoundation/hardhat-ethers@3.1.3(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(@nomicfoundation/hardhat-ignition@0.15.16(@nomicfoundation/hardhat-verify@2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)))(@nomicfoundation/ignition-core@0.15.15)(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-network-helpers': 1.1.2(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@nomicfoundation/hardhat-verify': 2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + '@typechain/ethers-v6': 0.5.1(ethers@6.16.0)(typechain@8.3.2(typescript@6.0.3))(typescript@6.0.3) + '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.16.0)(typechain@8.3.2(typescript@6.0.3))(typescript@6.0.3))(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(typechain@8.3.2(typescript@6.0.3)) + '@types/chai': 4.3.20 + '@types/mocha': 10.0.10 + '@types/node': 25.6.0 + chai: 4.5.0 + ethers: 6.16.0 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + hardhat-gas-reporter: 1.0.10(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + solidity-coverage: 0.8.17(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)) + ts-node: 10.9.2(@types/node@25.6.0)(typescript@6.0.3) + typechain: 8.3.2(typescript@6.0.3) + typescript: 6.0.3 + + '@nomicfoundation/hardhat-verify@2.1.3(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/address': 5.8.0 + cbor: 8.1.0 + debug: 4.4.3(supports-color@8.1.1) + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + lodash.clonedeep: 4.5.0 + picocolors: 1.1.1 + semver: 6.3.1 + table: 6.9.0 + undici: 5.29.0 + transitivePeerDependencies: + - supports-color + + '@nomicfoundation/ignition-core@0.15.15': + dependencies: + '@ethersproject/address': 5.6.1 + '@nomicfoundation/solidity-analyzer': 0.1.2 + cbor: 9.0.2 + debug: 4.4.3(supports-color@8.1.1) + ethers: 6.16.0 + fs-extra: 10.1.0 + immer: 10.0.2 + lodash: 4.17.21 + ndjson: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@nomicfoundation/ignition-ui@0.15.13': {} + + '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.2': + optional: true + + '@nomicfoundation/solidity-analyzer-darwin-x64@0.1.2': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.2': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.2': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.2': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.2': + optional: true + + '@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.2': + optional: true + + '@nomicfoundation/solidity-analyzer@0.1.2': + optionalDependencies: + '@nomicfoundation/solidity-analyzer-darwin-arm64': 0.1.2 + '@nomicfoundation/solidity-analyzer-darwin-x64': 0.1.2 + '@nomicfoundation/solidity-analyzer-linux-arm64-gnu': 0.1.2 + '@nomicfoundation/solidity-analyzer-linux-arm64-musl': 0.1.2 + '@nomicfoundation/solidity-analyzer-linux-x64-gnu': 0.1.2 + '@nomicfoundation/solidity-analyzer-linux-x64-musl': 0.1.2 + '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.2 + + '@openzeppelin/contracts-upgradeable@5.6.1(@openzeppelin/contracts@5.6.1)': + dependencies: + '@openzeppelin/contracts': 5.6.1 + + '@openzeppelin/contracts@5.6.1': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@scure/base@1.1.9': {} + + '@scure/base@1.2.6': {} + + '@scure/bip32@1.1.5': + dependencies: + '@noble/hashes': 1.2.0 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.1.9 + + '@scure/bip32@1.4.0': + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.1.1': + dependencies: + '@noble/hashes': 1.2.0 + '@scure/base': 1.1.9 + + '@scure/bip39@1.3.0': + dependencies: + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@sentry/core@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/minimal': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/hub@5.30.0': + dependencies: + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/minimal@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/types': 5.30.0 + tslib: 1.14.1 + + '@sentry/node@5.30.0': + dependencies: + '@sentry/core': 5.30.0 + '@sentry/hub': 5.30.0 + '@sentry/tracing': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + cookie: 0.4.2 + https-proxy-agent: 5.0.1 + lru_map: 0.3.3 + tslib: 1.14.1 + transitivePeerDependencies: + - supports-color + + '@sentry/tracing@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/minimal': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/types@5.30.0': {} + + '@sentry/utils@5.30.0': + dependencies: + '@sentry/types': 5.30.0 + tslib: 1.14.1 + + '@solidity-parser/parser@0.14.5': + dependencies: + antlr4ts: 0.5.0-alpha.4 + + '@solidity-parser/parser@0.20.2': {} + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@typechain/ethers-v6@0.5.1(ethers@6.16.0)(typechain@8.3.2(typescript@6.0.3))(typescript@6.0.3)': + dependencies: + ethers: 6.16.0 + lodash: 4.18.1 + ts-essentials: 7.0.3(typescript@6.0.3) + typechain: 8.3.2(typescript@6.0.3) + typescript: 6.0.3 + + '@typechain/hardhat@9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.16.0)(typechain@8.3.2(typescript@6.0.3))(typescript@6.0.3))(ethers@6.16.0)(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3))(typechain@8.3.2(typescript@6.0.3))': + dependencies: + '@typechain/ethers-v6': 0.5.1(ethers@6.16.0)(typechain@8.3.2(typescript@6.0.3))(typescript@6.0.3) + ethers: 6.16.0 + fs-extra: 9.1.0 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + typechain: 8.3.2(typescript@6.0.3) + + '@types/bn.js@5.2.0': + dependencies: + '@types/node': 25.6.0 + + '@types/chai-as-promised@7.1.8': + dependencies: + '@types/chai': 4.3.20 + + '@types/chai@4.3.20': {} + + '@types/concat-stream@1.6.1': + dependencies: + '@types/node': 25.6.0 + + '@types/form-data@0.0.33': + dependencies: + '@types/node': 25.6.0 + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 6.0.0 + '@types/node': 25.6.0 + + '@types/minimatch@6.0.0': + dependencies: + minimatch: 10.2.5 + + '@types/mocha@10.0.10': {} + + '@types/node@10.17.60': {} + + '@types/node@22.7.5': + dependencies: + undici-types: 6.19.8 + + '@types/node@25.6.0': + dependencies: + undici-types: 7.19.2 + + '@types/node@8.10.66': {} + + '@types/pbkdf2@3.1.2': + dependencies: + '@types/node': 25.6.0 + + '@types/prettier@2.7.3': {} + + '@types/qs@6.15.0': {} + + '@types/secp256k1@4.0.7': + dependencies: + '@types/node': 25.6.0 + + abbrev@1.0.9: {} + + abitype@1.2.3(typescript@6.0.3)(zod@4.3.6): + optionalDependencies: + typescript: 6.0.3 + zod: 4.3.6 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + adm-zip@0.4.16: {} + + aes-js@3.0.0: {} + + aes-js@4.0.0-beta.5: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + amdefine@1.0.1: + optional: true + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@3.0.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + antlr4ts@0.5.0-alpha.4: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-back@3.1.0: {} + + array-back@4.0.2: {} + + array-union@2.1.0: {} + + array-uniq@1.0.3: {} + + asap@2.0.6: {} + + assertion-error@1.1.0: {} + + astral-regex@2.0.0: {} + + async@1.5.2: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.15.1: + dependencies: + follow-redirects: 1.16.0(debug@4.4.3) + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base-x@3.0.11: + dependencies: + safe-buffer: 5.2.1 + + bech32@1.1.4: {} + + binary-extensions@2.3.0: {} + + blakejs@1.2.1: {} + + bn.js@4.11.6: {} + + bn.js@4.12.3: {} + + bn.js@5.2.3: {} + + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brorand@1.1.0: {} + + browser-stdout@1.3.1: {} + + browserify-aes@1.2.0: + dependencies: + buffer-xor: 1.0.3 + cipher-base: 1.0.7 + create-hash: 1.2.0 + evp_bytestokey: 1.0.3 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + bs58@4.0.1: + dependencies: + base-x: 3.0.11 + + bs58check@2.1.2: + dependencies: + bs58: 4.0.1 + create-hash: 1.2.0 + safe-buffer: 5.2.1 + + buffer-from@1.1.2: {} + + buffer-xor@1.0.3: {} + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase@6.3.0: {} + + caseless@0.12.0: {} + + cbor@8.1.0: + dependencies: + nofilter: 3.1.0 + + cbor@9.0.2: + dependencies: + nofilter: 3.1.0 + + chai-as-promised@7.1.2(chai@4.5.0): + dependencies: + chai: 4.5.0 + check-error: 1.0.3 + + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + charenc@0.0.2: {} + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@2.0.0: {} + + cipher-base@1.0.7: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + clean-stack@2.2.0: {} + + cli-boxes@2.2.1: {} + + cli-table3@0.5.1: + dependencies: + object-assign: 4.1.1 + string-width: 2.1.1 + optionalDependencies: + colors: 1.4.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + colors@1.4.0: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + command-exists@1.2.9: {} + + command-line-args@5.2.1: + dependencies: + array-back: 3.1.0 + find-replace: 3.0.0 + lodash.camelcase: 4.3.0 + typical: 4.0.0 + + command-line-usage@6.1.3: + dependencies: + array-back: 4.0.2 + chalk: 2.4.2 + table-layout: 1.0.2 + typical: 5.2.0 + + commander@8.3.0: {} + + concat-map@0.0.1: {} + + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + + cookie@0.4.2: {} + + core-util-is@1.0.3: {} + + create-hash@1.2.0: + dependencies: + cipher-base: 1.0.7 + inherits: 2.0.4 + md5.js: 1.3.5 + ripemd160: 2.0.3 + sha.js: 2.4.12 + + create-hmac@1.1.7: + dependencies: + cipher-base: 1.0.7 + create-hash: 1.2.0 + inherits: 2.0.4 + ripemd160: 2.0.3 + safe-buffer: 5.2.1 + sha.js: 2.4.12 + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypt@0.0.2: {} + + death@1.1.0: {} + + debug@4.4.3(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} + + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + + deep-extend@0.6.0: {} + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + diff@4.0.4: {} + + diff@5.2.2: {} + + difflib@0.2.4: + dependencies: + heap: 0.2.7 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + elliptic@6.6.1: + dependencies: + bn.js: 4.12.3 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + env-paths@2.2.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escodegen@1.8.1: + dependencies: + esprima: 2.7.3 + estraverse: 1.9.3 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.2.0 + + esprima@2.7.3: {} + + esprima@4.0.1: {} + + estraverse@1.9.3: {} + + esutils@2.0.3: {} + + eth-gas-reporter@0.2.27: + dependencies: + '@solidity-parser/parser': 0.14.5 + axios: 1.15.1 + cli-table3: 0.5.1 + colors: 1.4.0 + ethereum-cryptography: 1.2.0 + ethers: 5.8.0 + fs-readdir-recursive: 1.1.0 + lodash: 4.18.1 + markdown-table: 1.1.3 + mocha: 10.8.2 + req-cwd: 2.0.0 + sha1: 1.1.1 + sync-request: 6.1.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + ethereum-bloom-filters@1.2.0: + dependencies: + '@noble/hashes': 1.8.0 + + ethereum-cryptography@0.1.3: + dependencies: + '@types/pbkdf2': 3.1.2 + '@types/secp256k1': 4.0.7 + blakejs: 1.2.1 + browserify-aes: 1.2.0 + bs58check: 2.1.2 + create-hash: 1.2.0 + create-hmac: 1.1.7 + hash.js: 1.1.7 + keccak: 3.0.4 + pbkdf2: 3.1.5 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + scrypt-js: 3.0.1 + secp256k1: 4.0.4 + setimmediate: 1.0.5 + + ethereum-cryptography@1.2.0: + dependencies: + '@noble/hashes': 1.2.0 + '@noble/secp256k1': 1.7.1 + '@scure/bip32': 1.1.5 + '@scure/bip39': 1.1.1 + + ethereum-cryptography@2.2.1: + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + + ethereumjs-util@7.1.5: + dependencies: + '@types/bn.js': 5.2.0 + bn.js: 5.2.3 + create-hash: 1.2.0 + ethereum-cryptography: 0.1.3 + rlp: 2.2.7 + + ethers@5.8.0: + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/base64': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/contracts': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/json-wallets': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/providers': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/solidity': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/units': 5.8.0 + '@ethersproject/wallet': 5.8.0 + '@ethersproject/web': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ethers@6.16.0: + dependencies: + '@adraffy/ens-normalize': 1.10.1 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 22.7.5 + aes-js: 4.0.0-beta.5 + tslib: 2.7.0 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ethjs-unit@0.1.6: + dependencies: + bn.js: 4.11.6 + number-to-bn: 1.7.0 + + eventemitter3@5.0.1: {} + + evp_bytestokey@1.0.3: + dependencies: + md5.js: 1.3.5 + safe-buffer: 5.2.1 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.0: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-replace@3.0.0: + dependencies: + array-back: 3.1.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat@5.0.2: {} + + follow-redirects@1.16.0(debug@4.4.3): + optionalDependencies: + debug: 4.4.3(supports-color@8.1.1) + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@2.5.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + safe-buffer: 5.2.1 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + + fp-ts@1.19.3: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-readdir-recursive@1.1.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-func-name@2.0.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-port@3.2.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + ghost-testrpc@0.0.2: + dependencies: + chalk: 2.4.2 + node-emoji: 1.11.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@5.0.15: + dependencies: + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@7.1.7: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globby@10.0.2: + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + glob: 7.2.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + hardhat-gas-reporter@1.0.10(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)): + dependencies: + array-uniq: 1.0.3 + eth-gas-reporter: 0.2.27 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + sha1: 1.1.1 + transitivePeerDependencies: + - '@codechecks/client' + - bufferutil + - debug + - utf-8-validate + + hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3): + dependencies: + '@ethereumjs/util': 9.1.0 + '@ethersproject/abi': 5.8.0 + '@nomicfoundation/edr': 0.12.0-next.23 + '@nomicfoundation/solidity-analyzer': 0.1.2 + '@sentry/node': 5.30.0 + adm-zip: 0.4.16 + aggregate-error: 3.1.0 + ansi-escapes: 4.3.2 + boxen: 5.1.2 + chokidar: 4.0.3 + ci-info: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) + enquirer: 2.4.1 + env-paths: 2.2.1 + ethereum-cryptography: 1.2.0 + find-up: 5.0.0 + fp-ts: 1.19.3 + fs-extra: 7.0.1 + immutable: 4.3.8 + io-ts: 1.10.4 + json-stream-stringify: 3.1.6 + keccak: 3.0.4 + lodash: 4.18.1 + micro-eth-signer: 0.14.0 + mnemonist: 0.38.5 + mocha: 10.8.2 + p-map: 4.0.0 + picocolors: 1.1.1 + raw-body: 2.5.3 + resolve: 1.17.0 + semver: 6.3.1 + solc: 0.8.26(debug@4.4.3) + source-map-support: 0.5.21 + stacktrace-parser: 0.1.11 + tinyglobby: 0.2.16 + tsort: 0.0.1 + undici: 5.29.0 + uuid: 8.3.2 + ws: 7.5.10 + optionalDependencies: + ts-node: 10.9.2(@types/node@25.6.0)(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + has-flag@1.0.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hash-base@3.1.2: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + heap@0.2.7: {} + + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + http-basic@8.1.3: + dependencies: + caseless: 0.12.0 + concat-stream: 1.6.2 + http-response-object: 3.0.2 + parse-cache-control: 1.0.1 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-response-object@3.0.2: + dependencies: + '@types/node': 10.17.60 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + idb-keyval@6.2.2: {} + + iframe-shared-storage@1.0.34: + dependencies: + idb-keyval: 6.2.2 + postmsg-rpc: 2.4.0 + + ignore@5.3.2: {} + + immer@10.0.2: {} + + immer@10.2.0: {} + + immutable@4.3.8: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + interpret@1.4.0: {} + + io-ts@1.10.4: + dependencies: + fp-ts: 1.19.3 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.3 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@2.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hex-prefixed@1.0.0: {} + + is-number@7.0.0: {} + + is-plain-obj@2.1.0: {} + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-unicode-supported@0.1.0: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isows@1.0.7(ws@8.18.3): + dependencies: + ws: 8.18.3 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + js-sha3@0.8.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@1.0.0: {} + + json-stream-stringify@3.1.6: {} + + json-stringify-safe@5.0.1: {} + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonschema@1.5.0: {} + + keccak@3.0.4: + dependencies: + node-addon-api: 2.0.2 + node-gyp-build: 4.8.4 + readable-stream: 3.6.2 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + levn@0.3.0: + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.clonedeep@4.5.0: {} + + lodash.isequal@4.5.0: {} + + lodash.truncate@4.4.2: {} + + lodash@4.17.21: {} + + lodash@4.18.1: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + + lru-cache@10.4.3: {} + + lru_map@0.3.3: {} + + make-error@1.3.6: {} + + markdown-table@1.1.3: {} + + math-intrinsics@1.1.0: {} + + md5.js@1.3.5: + dependencies: + hash-base: 3.1.2 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + memorystream@0.3.1: {} + + merge2@1.4.1: {} + + micro-eth-signer@0.14.0: + dependencies: + '@noble/curves': 1.8.2 + '@noble/hashes': 1.7.2 + micro-packed: 0.7.3 + + micro-ftch@0.3.1: {} + + micro-packed@0.7.3: + dependencies: + '@scure/base': 1.2.6 + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.0 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.0 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + mnemonist@0.38.5: + dependencies: + obliterator: 2.0.5 + + mocha@10.8.2: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.3(supports-color@8.1.1) + diff: 5.2.2 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.1 + log-symbols: 4.1.0 + minimatch: 5.1.9 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + ndjson@2.0.0: + dependencies: + json-stringify-safe: 5.0.1 + minimist: 1.2.8 + readable-stream: 3.6.2 + split2: 3.2.2 + through2: 4.0.2 + + neo-async@2.6.2: {} + + node-addon-api@2.0.2: {} + + node-addon-api@5.1.0: {} + + node-emoji@1.11.0: + dependencies: + lodash: 4.18.1 + + node-gyp-build@4.8.4: {} + + node-tfhe@0.11.1: {} + + nofilter@3.1.0: {} + + nopt@3.0.6: + dependencies: + abbrev: 1.0.9 + + normalize-path@3.0.0: {} + + number-to-bn@1.7.0: + dependencies: + bn.js: 4.11.6 + strip-hex-prefix: 1.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + obliterator@2.0.5: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.8.3: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.5 + + ordinal@1.0.3: {} + + os-tmpdir@1.0.2: {} + + ox@0.14.17(typescript@6.0.3)(zod@4.3.6): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@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(typescript@6.0.3)(zod@4.3.6) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - zod + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parse-cache-control@1.0.1: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-type@4.0.0: {} + + pathval@1.1.1: {} + + pbkdf2@3.1.5: + dependencies: + create-hash: 1.2.0 + create-hmac: 1.1.7 + ripemd160: 2.0.3 + safe-buffer: 5.2.1 + sha.js: 2.4.12 + to-buffer: 1.2.2 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pify@4.0.1: {} + + possible-typed-array-names@1.1.0: {} + + postmsg-rpc@2.4.0: + dependencies: + shortid: 2.2.17 + + prelude-ls@1.1.2: {} + + prettier@2.8.8: {} + + process-nextick-args@2.0.1: {} + + promise@8.3.0: + dependencies: + asap: 2.0.6 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proxy-from-env@2.1.0: {} + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + readdirp@4.1.2: {} + + rechoir@0.6.2: + dependencies: + resolve: 1.22.12 + + recursive-readdir@2.2.3: + dependencies: + minimatch: 3.1.5 + + reduce-flatten@2.0.0: {} + + req-cwd@2.0.0: + dependencies: + req-from: 2.0.0 + + req-from@2.0.0: + dependencies: + resolve-from: 3.0.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@3.0.0: {} + + resolve@1.1.7: {} + + resolve@1.17.0: + dependencies: + path-parse: 1.0.7 + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rimraf@5.0.10: + dependencies: + glob: 10.5.0 + + ripemd160@2.0.3: + dependencies: + hash-base: 3.1.2 + inherits: 2.0.4 + + rlp@2.2.7: + dependencies: + bn.js: 5.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sc-istanbul@0.4.6: + dependencies: + abbrev: 1.0.9 + async: 1.5.2 + escodegen: 1.8.1 + esprima: 2.7.3 + glob: 5.0.15 + handlebars: 4.7.9 + js-yaml: 3.14.2 + mkdirp: 0.5.6 + nopt: 3.0.6 + once: 1.4.0 + resolve: 1.1.7 + supports-color: 3.2.3 + which: 1.3.1 + wordwrap: 1.0.0 + + scrypt-js@3.0.1: {} + + secp256k1@4.0.4: + dependencies: + elliptic: 6.6.1 + node-addon-api: 5.1.0 + node-gyp-build: 4.8.4 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + setimmediate@1.0.5: {} + + setprototypeof@1.2.0: {} + + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + sha1@1.1.1: + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shelljs@0.8.5: + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + + shortid@2.2.17: + dependencies: + nanoid: 3.3.11 + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + solc@0.8.26(debug@4.4.3): + dependencies: + command-exists: 1.2.9 + commander: 8.3.0 + follow-redirects: 1.16.0(debug@4.4.3) + js-sha3: 0.8.0 + memorystream: 0.3.1 + semver: 5.7.2 + tmp: 0.0.33 + transitivePeerDependencies: + - debug + + solidity-coverage@0.8.17(hardhat@2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3)): + dependencies: + '@ethersproject/abi': 5.8.0 + '@solidity-parser/parser': 0.20.2 + chalk: 2.4.2 + death: 1.1.0 + difflib: 0.2.4 + fs-extra: 8.1.0 + ghost-testrpc: 0.0.2 + global-modules: 2.0.0 + globby: 10.0.2 + hardhat: 2.28.6(ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3))(typescript@6.0.3) + jsonschema: 1.5.0 + lodash: 4.18.1 + mocha: 10.8.2 + node-emoji: 1.11.0 + pify: 4.0.1 + recursive-readdir: 2.2.3 + sc-istanbul: 0.4.6 + semver: 7.7.4 + shelljs: 0.8.5 + web3-utils: 1.10.4 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.2.0: + dependencies: + amdefine: 1.0.1 + optional: true + + source-map@0.6.1: {} + + split2@3.2.2: + dependencies: + readable-stream: 3.6.2 + + sprintf-js@1.0.3: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + statuses@2.0.2: {} + + string-format@2.0.0: {} + + string-width@2.1.1: + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@4.0.0: + dependencies: + ansi-regex: 3.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-hex-prefix@1.0.0: + dependencies: + is-hex-prefixed: 1.0.0 + + strip-json-comments@3.1.1: {} + + supports-color@3.2.3: + dependencies: + has-flag: 1.0.0 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + sync-request@6.1.0: + dependencies: + http-response-object: 3.0.2 + sync-rpc: 1.3.6 + then-request: 6.0.2 + + sync-rpc@1.3.6: + dependencies: + get-port: 3.2.0 + + table-layout@1.0.2: + dependencies: + array-back: 4.0.2 + deep-extend: 0.6.0 + typical: 5.2.0 + wordwrapjs: 4.0.1 + + table@6.9.0: + dependencies: + ajv: 8.18.0 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tfhe@0.11.1: {} + + then-request@6.0.2: + dependencies: + '@types/concat-stream': 1.6.1 + '@types/form-data': 0.0.33 + '@types/node': 8.10.66 + '@types/qs': 6.15.0 + caseless: 0.12.0 + concat-stream: 1.6.2 + form-data: 2.5.5 + http-basic: 8.1.3 + http-response-object: 3.0.2 + promise: 8.3.0 + qs: 6.15.1 + + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + ts-command-line-args@2.5.1: + dependencies: + chalk: 4.1.2 + command-line-args: 5.2.1 + command-line-usage: 6.1.3 + string-format: 2.0.0 + + ts-essentials@7.0.3(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + ts-node@10.9.2(@types/node@25.6.0)(typescript@6.0.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 25.6.0 + acorn: 8.16.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 6.0.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@1.14.1: {} + + tslib@2.7.0: {} + + tsort@0.0.1: {} + + tweetnacl@1.0.3: {} + + type-check@0.3.2: + dependencies: + prelude-ls: 1.1.2 + + type-detect@4.1.0: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.7.1: {} + + typechain@8.3.2(typescript@6.0.3): + dependencies: + '@types/prettier': 2.7.3 + debug: 4.4.3(supports-color@8.1.1) + fs-extra: 7.0.1 + glob: 7.1.7 + js-sha3: 0.8.0 + lodash: 4.18.1 + mkdirp: 1.0.4 + prettier: 2.8.8 + ts-command-line-args: 2.5.1 + ts-essentials: 7.0.3(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typedarray@0.0.6: {} + + typescript@6.0.3: {} + + typical@4.0.0: {} + + typical@5.2.0: {} + + uglify-js@3.19.3: + optional: true + + undici-types@6.19.8: {} + + undici-types@7.19.2: {} + + undici@5.29.0: + dependencies: + '@fastify/busboy': 2.1.1 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + utf8@3.0.0: {} + + util-deprecate@1.0.2: {} + + uuid@8.3.2: {} + + v8-compile-cache-lib@3.0.1: {} + + viem@2.48.1(typescript@6.0.3)(zod@4.3.6): + 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(typescript@6.0.3)(zod@4.3.6) + isows: 1.0.7(ws@8.18.3) + ox: 0.14.17(typescript@6.0.3)(zod@4.3.6) + ws: 8.18.3 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + + web3-utils@1.10.4: + dependencies: + '@ethereumjs/util': 8.1.0 + bn.js: 5.2.3 + ethereum-bloom-filters: 1.2.0 + ethereum-cryptography: 2.2.1 + ethjs-unit: 0.1.6 + number-to-bn: 1.7.0 + randombytes: 2.1.0 + utf8: 3.0.0 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + wordwrapjs@4.0.1: + dependencies: + reduce-flatten: 2.0.0 + typical: 5.2.0 + + workerpool@6.5.1: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + ws@7.5.10: {} + + ws@8.17.1: {} + + ws@8.18.0: {} + + ws@8.18.3: {} + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod@4.3.6: {} + + zustand@5.0.12(immer@10.2.0): + optionalDependencies: + immer: 10.2.0 diff --git a/fhe/scripts/deploy-triage.ts b/fhe/scripts/deploy-triage.ts new file mode 100644 index 0000000..fff26cd --- /dev/null +++ b/fhe/scripts/deploy-triage.ts @@ -0,0 +1,48 @@ +import hre from "hardhat"; +import fs from "fs"; +import path from "path"; + +async function main() { + const [deployer] = await hre.ethers.getSigners(); + console.log("Deploying HackTriage with account:", deployer.address); + + const balance = await hre.ethers.provider.getBalance(deployer.address); + console.log("Account balance:", hre.ethers.formatEther(balance), "ETH"); + + const factory = await hre.ethers.getContractFactory("HackTriage"); + const contract = await factory.deploy(); + await contract.waitForDeployment(); + + const address = await contract.getAddress(); + const deployTx = contract.deploymentTransaction(); + const receipt = await deployTx?.wait(); + if (!receipt) throw new Error("Deploy transaction receipt is null — deployment may have failed"); + const blockNumber = receipt.blockNumber; + + console.log("HackTriage deployed to:", address); + console.log("Block number:", blockNumber); + + // Write deployment record + const deploymentsDir = path.join(__dirname, "..", "deployments"); + if (!fs.existsSync(deploymentsDir)) { + fs.mkdirSync(deploymentsDir, { recursive: true }); + } + + const deploymentData = { + HackTriage: address, + block: blockNumber, + deployer: deployer.address, + network: hre.network.name, + chainId: hre.network.config.chainId, + timestamp: new Date().toISOString(), + }; + + const outPath = path.join(deploymentsDir, `${hre.network.name}.json`); + fs.writeFileSync(outPath, JSON.stringify(deploymentData, null, 2)); + console.log("Deployment recorded at:", outPath); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/fhe/scripts/deployRiskThrottle.ts b/fhe/scripts/deployRiskThrottle.ts new file mode 100644 index 0000000..44c7d56 --- /dev/null +++ b/fhe/scripts/deployRiskThrottle.ts @@ -0,0 +1,65 @@ +import hre from "hardhat"; +import fs from "fs"; +import path from "path"; + +// Read HackTriage address from deployments/sepolia.json, or fall back to env var override +function getHackTriageAddress(): string { + const envAddr = process.env.TRIAGE_ADDR; + if (envAddr) return envAddr; + + const sepoliaPath = path.join(__dirname, "..", "deployments", "sepolia.json"); + if (fs.existsSync(sepoliaPath)) { + const data = JSON.parse(fs.readFileSync(sepoliaPath, "utf-8")); + if (data.HackTriage) return data.HackTriage; + } + throw new Error("HackTriage address not found. Set TRIAGE_ADDR env var or deploy HackTriage first."); +} + +async function main() { + const HACK_TRIAGE_ADDRESS = getHackTriageAddress(); + const [deployer] = await hre.ethers.getSigners(); + console.log("Deploying RiskThrottle with account:", deployer.address); + + const balance = await hre.ethers.provider.getBalance(deployer.address); + console.log("Account balance:", hre.ethers.formatEther(balance), "ETH"); + + console.log("HackTriage address:", HACK_TRIAGE_ADDRESS); + + const factory = await hre.ethers.getContractFactory("RiskThrottle"); + const contract = await factory.deploy(HACK_TRIAGE_ADDRESS); + await contract.waitForDeployment(); + + const address = await contract.getAddress(); + const deployTx = contract.deploymentTransaction(); + const receipt = await deployTx?.wait(); + if (!receipt) throw new Error("Deploy transaction receipt is null — deployment may have failed"); + const blockNumber = receipt.blockNumber; + + console.log("RiskThrottle deployed to:", address); + console.log("Block number:", blockNumber); + + // Write deployment record (merge into existing sepolia.json) + const deploymentsDir = path.join(__dirname, "..", "deployments"); + if (!fs.existsSync(deploymentsDir)) { + fs.mkdirSync(deploymentsDir, { recursive: true }); + } + + const outPath = path.join(deploymentsDir, `throttle-${hre.network.name}.json`); + const deploymentData = { + RiskThrottle: address, + HackTriage: HACK_TRIAGE_ADDRESS, + block: blockNumber, + deployer: deployer.address, + network: hre.network.name, + chainId: hre.network.config.chainId, + timestamp: new Date().toISOString(), + }; + + fs.writeFileSync(outPath, JSON.stringify(deploymentData, null, 2)); + console.log("Deployment recorded at:", outPath); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/fhe/tsconfig.json b/fhe/tsconfig.json new file mode 100644 index 0000000..ed8b90d --- /dev/null +++ b/fhe/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "rootDir": ".", + "outDir": "./dist" + }, + "include": ["./scripts", "./test", "./typechain-types", "hardhat.config.ts"] +} diff --git a/index.html b/index.html index 6746f42..aef4ac2 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + diff --git a/package-lock.json b/package-lock.json index 840da91..52a0086 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,15 @@ "name": "web3-toolkit", "version": "0.0.0", "dependencies": { + "@cofhe/sdk": "^0.4.0", "@dynamic-labs/ethereum": "^4.30.3", "@dynamic-labs/sdk-react-core": "^4.30.3", "@dynamic-labs/wagmi-connector": "^4.30.3", "@monaco-editor/react": "^4.7.0", "@phosphor-icons/react": "^2.1.10", - "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-hover-card": "^1.1.15", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-radio-group": "^1.2.3", @@ -32,9 +30,7 @@ "@shazow/whatsabi": "^0.22.2", "@solidity-parser/parser": "^0.20.2", "@tanstack/react-query": "^5.87.4", - "@types/react-router-dom": "^5.3.3", - "@web3modal/siwe": "^5.1.11", - "@web3modal/wagmi": "^5.1.11", + "@wagmi/core": "^2.20.3", "@xyflow/react": "^12.10.2", "animejs": "^4.3.5", "axios": "^1.11.0", @@ -42,11 +38,12 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "dompurify": "^3.3.1", - "dotenv": "^17.2.2", "ethers": "^5.7.2", "framer-motion": "^12.38.0", + "iframe-shared-storage": "^1.0.34", "json-edit-react": "^1.28.2", "lucide-react": "^0.542.0", + "monaco-editor": "^0.55.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19.1.1", @@ -57,6 +54,7 @@ "react-window": "^2.2.3", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", + "tfhe": "^0.11.1", "viem": "^2.37.3", "wagmi": "^2.16.9", "ws": "^8.18.3", @@ -68,20 +66,17 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/dompurify": "^3.0.5", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", - "@types/react-window": "^1.8.8", - "@vercel/node": "^5.6.9", + "@vercel/node": "^5.7.6", "@vitejs/plugin-react": "^4.7.0", - "baseline-browser-mapping": "^2.10.0", "eslint": "^9.33.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", + "fake-indexeddb": "^6.2.5", "globals": "^16.3.0", - "happy-dom": "^20.3.3", + "happy-dom": "^20.9.0", "jsdom": "^27.0.1", - "shadcn": "^3.6.2", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", "typescript": "~5.8.3", @@ -141,27 +136,6 @@ "node": ">=6.0.0" } }, - "node_modules/@antfu/ni": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-25.0.0.tgz", - "integrity": "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==", - "dev": true, - "dependencies": { - "ansis": "^4.0.0", - "fzf": "^0.5.2", - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "bin": { - "na": "bin/na.mjs", - "nci": "bin/nci.mjs", - "ni": "bin/ni.mjs", - "nlx": "bin/nlx.mjs", - "nr": "bin/nr.mjs", - "nun": "bin/nun.mjs", - "nup": "bin/nup.mjs" - } - }, "node_modules/@asamuzakjp/css-color": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", @@ -286,18 +260,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", @@ -314,27 +276,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -344,19 +285,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -387,18 +315,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", @@ -408,36 +324,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -493,52 +379,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", @@ -569,44 +409,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", - "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -691,6 +493,52 @@ "dev": true, "license": "(Apache-2.0 WITH LLVM-exception)" }, + "node_modules/@cofhe/sdk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cofhe/sdk/-/sdk-0.4.0.tgz", + "integrity": "sha512-L+X8S9sKV0pT7gMAx4OxluiKnjP3uPQVw1GN+HBE8lRo8icZcPyGU2LrZQoXvnTU04g+147zvBhoZ0cSCMj1/Q==", + "license": "MIT", + "dependencies": { + "iframe-shared-storage": "^1.0.34", + "immer": "^10.1.1", + "node-tfhe": "0.11.1", + "tfhe": "0.11.1", + "tweetnacl": "^1.0.3", + "viem": "^2.38.6", + "zod": "^4.0.0", + "zustand": "^5.0.1" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@wagmi/core": "^2.0.0", + "ethers": "^5.0.0 || ^6.0.0", + "hardhat": "^2.0.0", + "viem": "^2.38.6" + }, + "peerDependenciesMeta": { + "@nomicfoundation/hardhat-ethers": { + "optional": true + }, + "@wagmi/core": { + "optional": true + }, + "ethers": { + "optional": true + }, + "hardhat": { + "optional": true + } + } + }, + "node_modules/@cofhe/sdk/node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@coinbase/wallet-sdk": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.3.6.tgz", @@ -850,177 +698,6 @@ "node": ">=18" } }, - "node_modules/@dotenvx/dotenvx": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.0.tgz", - "integrity": "sha512-CbMGzyOYSyFF7d4uaeYwO9gpSBzLTnMmSmTVpCZjvpJFV69qYbjYPpzNnCz1mb2wIvEhjWjRwQWuBzTO0jITww==", - "dev": true, - "dependencies": { - "commander": "^11.1.0", - "dotenv": "^17.2.1", - "eciesjs": "^0.4.10", - "execa": "^5.1.1", - "fdir": "^6.2.0", - "ignore": "^5.3.0", - "object-treeify": "1.1.33", - "picomatch": "^4.0.2", - "which": "^4.0.0" - }, - "bin": { - "dotenvx": "src/cli/dotenvx.js" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@dotenvx/dotenvx/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, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, "node_modules/@dynamic-labs-connectors/base-account-evm": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@dynamic-labs-connectors/base-account-evm/-/base-account-evm-4.4.2.tgz", @@ -2039,6 +1716,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -2055,6 +1733,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2071,6 +1750,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2087,6 +1767,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -2103,6 +1784,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2119,6 +1801,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2135,6 +1818,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2151,6 +1835,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2167,6 +1852,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2183,6 +1869,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2199,6 +1886,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2215,6 +1903,7 @@ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2231,6 +1920,7 @@ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2247,6 +1937,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2263,6 +1954,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2279,6 +1971,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -2328,6 +2021,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2361,6 +2055,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2394,6 +2089,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -2410,6 +2106,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2426,6 +2123,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2442,6 +2140,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3556,6 +3255,7 @@ "cpu": [ "arm64" ], + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -3577,6 +3277,7 @@ "cpu": [ "x64" ], + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -3598,6 +3299,7 @@ "cpu": [ "arm64" ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -3613,6 +3315,7 @@ "cpu": [ "x64" ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -3628,6 +3331,10 @@ "cpu": [ "arm" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -3643,6 +3350,10 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -3658,6 +3369,10 @@ "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -3688,6 +3403,10 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -3718,6 +3437,10 @@ "cpu": [ "arm" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -3739,6 +3462,10 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -3760,6 +3487,10 @@ "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -3802,6 +3533,10 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -3844,6 +3579,7 @@ "cpu": [ "wasm32" ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { "@emnapi/runtime": "^1.2.0" @@ -3862,6 +3598,7 @@ "cpu": [ "ia32" ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -3880,6 +3617,7 @@ "cpu": [ "x64" ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -3891,89 +3629,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@inquirer/ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.0.tgz", - "integrity": "sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.18", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.18.tgz", - "integrity": "sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==", - "dev": true, - "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.2.tgz", - "integrity": "sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==", - "dev": true, - "dependencies": { - "@inquirer/ansi": "^1.0.0", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", - "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", - "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", - "dev": true, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@isaacs/balanced-match": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", @@ -4618,38 +4273,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.0.tgz", - "integrity": "sha512-kOQ4+fHuT4KbR2iq2IjeV32HiihueuOf1vJkq18z08CLZ1UQrTc8BXJpVfxZkq45+inLLD+D4xx4nBjUelJa4Q==", - "dev": true, - "dependencies": { - "ajv": "^6.12.6", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "dev": true, - "peerDependencies": { - "zod": "^3.24.1" - } - }, "node_modules/@monaco-editor/loader": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", @@ -4673,83 +4296,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@motionone/animation": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.18.0.tgz", - "integrity": "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==", - "dependencies": { - "@motionone/easing": "^10.18.0", - "@motionone/types": "^10.17.1", - "@motionone/utils": "^10.18.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/dom": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.18.0.tgz", - "integrity": "sha512-bKLP7E0eyO4B2UaHBBN55tnppwRnaE3KFfh3Ps9HhnAkar3Cb69kUCJY9as8LrccVYKgHA+JY5dOQqJLOPhF5A==", - "dependencies": { - "@motionone/animation": "^10.18.0", - "@motionone/generators": "^10.18.0", - "@motionone/types": "^10.17.1", - "@motionone/utils": "^10.18.0", - "hey-listen": "^1.0.8", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/easing": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.18.0.tgz", - "integrity": "sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==", - "dependencies": { - "@motionone/utils": "^10.18.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/generators": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.18.0.tgz", - "integrity": "sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==", - "dependencies": { - "@motionone/types": "^10.17.1", - "@motionone/utils": "^10.18.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/svelte": { - "version": "10.16.4", - "resolved": "https://registry.npmjs.org/@motionone/svelte/-/svelte-10.16.4.tgz", - "integrity": "sha512-zRVqk20lD1xqe+yEDZhMYgftsuHc25+9JSo+r0a0OWUJFocjSV9D/+UGhX4xgJsuwB9acPzXLr20w40VnY2PQA==", - "dependencies": { - "@motionone/dom": "^10.16.4", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/types": { - "version": "10.17.1", - "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.17.1.tgz", - "integrity": "sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==" - }, - "node_modules/@motionone/utils": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.18.0.tgz", - "integrity": "sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==", - "dependencies": { - "@motionone/types": "^10.17.1", - "hey-listen": "^1.0.8", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/vue": { - "version": "10.16.4", - "resolved": "https://registry.npmjs.org/@motionone/vue/-/vue-10.16.4.tgz", - "integrity": "sha512-z10PF9JV6SbjFq+/rYabM+8CVlMokgl8RFGvieSGNTmrkQanfHn+15XBrhG3BgUfvmTeSeyShfOHpG0i9zEdcg==", - "deprecated": "Motion One for Vue is deprecated. Use Oku Motion instead https://oku-ui.com/motion", - "dependencies": { - "@motionone/dom": "^10.16.4", - "tslib": "^2.3.1" - } - }, "node_modules/@msgpack/msgpack": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.2.tgz", @@ -4758,23 +4304,6 @@ "node": ">= 18" } }, - "node_modules/@mswjs/interceptors": { - "version": "0.39.7", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.7.tgz", - "integrity": "sha512-sURvQbbKsq5f8INV54YJgJEdk8oxBanqkTiXXd33rKmofFCwZLhLRszPduMZ9TA9b8/1CHc/IJmOlBHJk2Q5AQ==", - "dev": true, - "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@mysten/bcs": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-1.5.0.tgz", @@ -4895,28 +4424,6 @@ "node": ">= 8" } }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "dev": true - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dev": true, - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "dev": true - }, "node_modules/@openzeppelin/contracts": { "version": "4.9.6", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz", @@ -9100,9 +8607,9 @@ "license": "MIT" }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "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": { @@ -9120,6 +8627,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -9133,6 +8641,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -9146,6 +8655,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -9159,6 +8669,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -9172,6 +8683,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -9185,6 +8697,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -9198,6 +8711,10 @@ "arm" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9211,6 +8728,10 @@ "arm" ], "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9224,6 +8745,10 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9237,6 +8762,10 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9250,6 +8779,10 @@ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9263,6 +8796,10 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9276,6 +8813,10 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9289,6 +8830,10 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9302,6 +8847,10 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -9341,6 +8890,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openharmony" @@ -9354,6 +8904,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -9367,6 +8918,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -9380,6 +8932,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -9471,12 +9024,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true - }, "node_modules/@shazow/whatsabi": { "version": "0.22.2", "resolved": "https://registry.npmjs.org/@shazow/whatsabi/-/whatsabi-0.22.2.tgz", @@ -9628,18 +9175,6 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -9940,152 +9475,6 @@ "integrity": "sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==", "license": "MIT" }, - "node_modules/@stablelib/aead": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/aead/-/aead-1.0.1.tgz", - "integrity": "sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg==" - }, - "node_modules/@stablelib/binary": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-1.0.1.tgz", - "integrity": "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==", - "dependencies": { - "@stablelib/int": "^1.0.1" - } - }, - "node_modules/@stablelib/bytes": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/bytes/-/bytes-1.0.1.tgz", - "integrity": "sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ==" - }, - "node_modules/@stablelib/chacha": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/chacha/-/chacha-1.0.1.tgz", - "integrity": "sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg==", - "dependencies": { - "@stablelib/binary": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/chacha20poly1305": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/chacha20poly1305/-/chacha20poly1305-1.0.1.tgz", - "integrity": "sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA==", - "dependencies": { - "@stablelib/aead": "^1.0.1", - "@stablelib/binary": "^1.0.1", - "@stablelib/chacha": "^1.0.1", - "@stablelib/constant-time": "^1.0.1", - "@stablelib/poly1305": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/constant-time/-/constant-time-1.0.1.tgz", - "integrity": "sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg==" - }, - "node_modules/@stablelib/ed25519": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stablelib/ed25519/-/ed25519-1.0.3.tgz", - "integrity": "sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg==", - "dependencies": { - "@stablelib/random": "^1.0.2", - "@stablelib/sha512": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/hash": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/hash/-/hash-1.0.1.tgz", - "integrity": "sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg==" - }, - "node_modules/@stablelib/hkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/hkdf/-/hkdf-1.0.1.tgz", - "integrity": "sha512-SBEHYE16ZXlHuaW5RcGk533YlBj4grMeg5TooN80W3NpcHRtLZLLXvKyX0qcRFxf+BGDobJLnwkvgEwHIDBR6g==", - "dependencies": { - "@stablelib/hash": "^1.0.1", - "@stablelib/hmac": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/hmac": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/hmac/-/hmac-1.0.1.tgz", - "integrity": "sha512-V2APD9NSnhVpV/QMYgCVMIYKiYG6LSqw1S65wxVoirhU/51ACio6D4yDVSwMzuTJXWZoVHbDdINioBwKy5kVmA==", - "dependencies": { - "@stablelib/constant-time": "^1.0.1", - "@stablelib/hash": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/int": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz", - "integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==" - }, - "node_modules/@stablelib/keyagreement": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/keyagreement/-/keyagreement-1.0.1.tgz", - "integrity": "sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==", - "dependencies": { - "@stablelib/bytes": "^1.0.1" - } - }, - "node_modules/@stablelib/poly1305": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/poly1305/-/poly1305-1.0.1.tgz", - "integrity": "sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA==", - "dependencies": { - "@stablelib/constant-time": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/random": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@stablelib/random/-/random-1.0.2.tgz", - "integrity": "sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==", - "dependencies": { - "@stablelib/binary": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/sha256": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/sha256/-/sha256-1.0.1.tgz", - "integrity": "sha512-GIIH3e6KH+91FqGV42Kcj71Uefd/QEe7Dy42sBTeqppXV95ggCcxLTk39bEr+lZfJmp+ghsR07J++ORkRELsBQ==", - "dependencies": { - "@stablelib/binary": "^1.0.1", - "@stablelib/hash": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/sha512": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/sha512/-/sha512-1.0.1.tgz", - "integrity": "sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw==", - "dependencies": { - "@stablelib/binary": "^1.0.1", - "@stablelib/hash": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/wipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz", - "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==" - }, - "node_modules/@stablelib/x25519": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stablelib/x25519/-/x25519-1.0.3.tgz", - "integrity": "sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==", - "dependencies": { - "@stablelib/keyagreement": "^1.0.1", - "@stablelib/random": "^1.0.2", - "@stablelib/wipe": "^1.0.1" - } - }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -10227,6 +9616,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -10244,6 +9636,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -10524,43 +9919,17 @@ "integrity": "sha512-NKyqCvP6DZKlRf6aGfnKS6Kntn2gnuBxa/ztstjy+oo1t23EHzQ54shtli0yV5WAtygmK1tti/uL2C2p/kW3HQ==", "deprecated": "Please upgrade to v1" }, - "node_modules/@ts-morph/common": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", - "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", - "dev": true, + "node_modules/@turnkey/api-key-stamper": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@turnkey/api-key-stamper/-/api-key-stamper-0.4.7.tgz", + "integrity": "sha512-/0/kW7v+uCnmHnGMoHSXn4Vb/MxLAIivGxX/T0L4vVoIiJalQmqcCtgiWnPWZDiJNGjMKp+jd/8j6VXgbVVozg==", "dependencies": { - "fast-glob": "^3.3.3", - "minimatch": "^10.0.1", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dev": true, - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@turnkey/api-key-stamper": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@turnkey/api-key-stamper/-/api-key-stamper-0.4.7.tgz", - "integrity": "sha512-/0/kW7v+uCnmHnGMoHSXn4Vb/MxLAIivGxX/T0L4vVoIiJalQmqcCtgiWnPWZDiJNGjMKp+jd/8j6VXgbVVozg==", - "dependencies": { - "@noble/curves": "^1.3.0", - "@turnkey/encoding": "0.5.0", - "sha256-uint8array": "^0.10.7" - }, - "engines": { - "node": ">=18.0.0" + "@noble/curves": "^1.3.0", + "@turnkey/encoding": "0.5.0", + "sha256-uint8array": "^0.10.7" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@turnkey/crypto": { @@ -10884,27 +10253,12 @@ "@types/ms": "*" } }, - "node_modules/@types/dompurify": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", - "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/trusted-types": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -10933,6 +10287,7 @@ "version": "19.1.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", + "devOptional": true, "dependencies": { "csstype": "^3.0.2" } @@ -10946,41 +10301,6 @@ "@types/react": "^19.0.0" } }, - "node_modules/@types/react-router": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@types/react-window": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", - "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/statuses": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", - "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", - "dev": true - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -11317,15 +10637,24 @@ } }, "node_modules/@vercel/build-utils": { - "version": "13.6.1", - "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.6.1.tgz", - "integrity": "sha512-/qRDC8swTUDrdQLkKBnyY8TSk+DeI8RTOIhAba2BwCVCHaZoLF8+sdOCeHud1QJk/3a8R5rAmMQOvEI4P2FRZQ==", + "version": "13.19.1", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.19.1.tgz", + "integrity": "sha512-TRb8x9MuRrBYx7SjwW4hGnEMTnEPDk/jCQRjziS7AkPj0ahayqYlSjoZG3AWKc5NLOwulLekGbhQXUMRAdaA/A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@vercel/python-analysis": "0.8.1" + "@vercel/python-analysis": "0.11.0", + "cjs-module-lexer": "1.2.3", + "es-module-lexer": "1.5.0" } }, + "node_modules/@vercel/build-utils/node_modules/es-module-lexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==", + "dev": true, + "license": "MIT" + }, "node_modules/@vercel/error-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.0.3.tgz", @@ -11334,9 +10663,9 @@ "license": "Apache-2.0" }, "node_modules/@vercel/nft": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", - "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.5.0.tgz", + "integrity": "sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA==", "dev": true, "license": "MIT", "dependencies": { @@ -11368,9 +10697,9 @@ "license": "MIT" }, "node_modules/@vercel/nft/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "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": { @@ -11391,9 +10720,9 @@ } }, "node_modules/@vercel/node": { - "version": "5.6.9", - "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.6.9.tgz", - "integrity": "sha512-SiLToxNIGNSaELFhMorNAWIW1LkBCOEIw7+P3MxDxeaY9RAn5nqymT4uLg95L94JvI4dAFZbdfS7ntgmEfB64A==", + "version": "5.7.12", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.7.12.tgz", + "integrity": "sha512-ClU5ttdwMUr+IC43nNdRhHQR9XnIaF1SNUSJ0IrTQvqXt9ItLvS1zEDRF83VPLVFs4Rd6WtSB2kcGGsRzHjcEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11401,10 +10730,10 @@ "@edge-runtime/primitives": "4.1.0", "@edge-runtime/vm": "3.2.0", "@types/node": "20.11.0", - "@vercel/build-utils": "13.6.1", + "@vercel/build-utils": "13.19.1", "@vercel/error-utils": "2.0.3", - "@vercel/nft": "1.1.1", - "@vercel/static-config": "3.1.2", + "@vercel/nft": "1.5.0", + "@vercel/static-config": "3.2.0", "async-listen": "3.0.0", "cjs-module-lexer": "1.2.3", "edge-runtime": "2.5.9", @@ -11945,9 +11274,9 @@ "license": "MIT" }, "node_modules/@vercel/python-analysis": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@vercel/python-analysis/-/python-analysis-0.8.1.tgz", - "integrity": "sha512-gW1pZDqJaTcjZYPvNhXXLOPgLu6vJW9PKweJoX2f8EKAoW+JIiYncl8AddcSlngNhQRG7SqUl2u3qosZM4kUBA==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@vercel/python-analysis/-/python-analysis-0.11.0.tgz", + "integrity": "sha512-gsoj+nscmNm0xDh+tRhECRhit2VlAVaD7jc9h93sN6rDEBDxPo7eLEgIJFzVDaAItxERZ9Od2IK/04fB9vFy+g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11956,7 +11285,6 @@ "fs-extra": "11.1.1", "js-yaml": "4.1.1", "minimatch": "10.1.1", - "pip-requirements-js": "1.0.2", "smol-toml": "1.5.2", "zod": "3.22.4" } @@ -12003,9 +11331,9 @@ } }, "node_modules/@vercel/static-config": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.1.2.tgz", - "integrity": "sha512-2d+TXr6K30w86a+WbMbGm2W91O0UzO5VeemZYBBUJbCjk/5FLLGIi8aV6RS2+WmaRvtcqNTn2pUA7nCOK3bGcQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.2.0.tgz", + "integrity": "sha512-UpOEIgWxWx0M+mDe1IMdHS6JuWM/L5nNIJ4ixX8v9JgBAejymo88OkgnmfLCNMem0Wd+b5vcQPWLdZybCndlsA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12563,120 +11891,6 @@ "pino": "7.11.0" } }, - "node_modules/@walletconnect/modal": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@walletconnect/modal/-/modal-2.6.2.tgz", - "integrity": "sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA==", - "deprecated": "Please follow the migration guide on https://docs.reown.com/appkit/upgrade/wcm", - "dependencies": { - "@walletconnect/modal-core": "2.6.2", - "@walletconnect/modal-ui": "2.6.2" - } - }, - "node_modules/@walletconnect/modal-core": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@walletconnect/modal-core/-/modal-core-2.6.2.tgz", - "integrity": "sha512-cv8ibvdOJQv2B+nyxP9IIFdxvQznMz8OOr/oR/AaUZym4hjXNL/l1a2UlSQBXrVjo3xxbouMxLb3kBsHoYP2CA==", - "dependencies": { - "valtio": "1.11.2" - } - }, - "node_modules/@walletconnect/modal-core/node_modules/proxy-compare": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz", - "integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==" - }, - "node_modules/@walletconnect/modal-core/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==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@walletconnect/modal-core/node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@walletconnect/modal-core/node_modules/valtio": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.11.2.tgz", - "integrity": "sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==", - "dependencies": { - "proxy-compare": "2.5.1", - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@walletconnect/modal-ui": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@walletconnect/modal-ui/-/modal-ui-2.6.2.tgz", - "integrity": "sha512-rbdstM1HPGvr7jprQkyPggX7rP4XiCG85ZA+zWBEX0dVQg8PpAgRUqpeub4xQKDgY7pY/xLRXSiCVdWGqvG2HA==", - "dependencies": { - "@walletconnect/modal-core": "2.6.2", - "lit": "2.8.0", - "motion": "10.16.2", - "qrcode": "1.5.3" - } - }, - "node_modules/@walletconnect/modal-ui/node_modules/@lit/reactive-element": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", - "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.0.0" - } - }, - "node_modules/@walletconnect/modal-ui/node_modules/lit": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", - "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", - "dependencies": { - "@lit/reactive-element": "^1.6.0", - "lit-element": "^3.3.0", - "lit-html": "^2.8.0" - } - }, - "node_modules/@walletconnect/modal-ui/node_modules/lit-element": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", - "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.0", - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.8.0" - } - }, - "node_modules/@walletconnect/modal-ui/node_modules/lit-html": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", - "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, "node_modules/@walletconnect/relay-api": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", @@ -12994,760 +12208,81 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/@web3modal/base": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/base/-/base-5.1.11.tgz", - "integrity": "sha512-wJCsqQ1FG0Isiv0Exaz2Sv+FpijVmNPNay+sGdV5HP2SpBAR/1xxHca2/vLBdACX7rYAFAj723DYQE0fmUpIaw==", - "dependencies": { - "@walletconnect/utils": "2.16.1", - "@web3modal/common": "5.1.11", - "@web3modal/core": "5.1.11", - "@web3modal/polyfills": "5.1.11", - "@web3modal/scaffold-ui": "5.1.11", - "@web3modal/scaffold-utils": "5.1.11", - "@web3modal/siwe": "5.1.11", - "@web3modal/ui": "5.1.11", - "@web3modal/wallet": "5.1.11" - }, - "optionalDependencies": { - "borsh": "0.7.0", - "bs58": "5.0.0" - } - }, - "node_modules/@web3modal/base/node_modules/@walletconnect/relay-auth": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.0.4.tgz", - "integrity": "sha512-kKJcS6+WxYq5kshpPaxGHdwf5y98ZwbfuS4EE/NkQzqrDFm5Cj+dP8LofzWvjrrLkZq7Afy7WrQMXdLy8Sx7HQ==", - "dependencies": { - "@stablelib/ed25519": "^1.0.2", - "@stablelib/random": "^1.0.1", - "@walletconnect/safe-json": "^1.0.1", - "@walletconnect/time": "^1.0.2", - "tslib": "1.14.1", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/@web3modal/base/node_modules/@walletconnect/types": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.16.1.tgz", - "integrity": "sha512-9P4RG4VoDEF+yBF/n2TF12gsvT/aTaeZTVDb/AOayafqiPnmrQZMKmNCJJjq1sfdsDcHXFcZWMGsuCeSJCmrXA==", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "events": "3.3.0" - } - }, - "node_modules/@web3modal/base/node_modules/@walletconnect/utils": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.16.1.tgz", - "integrity": "sha512-aoQirVoDoiiEtYeYDtNtQxFzwO/oCrz9zqeEEXYJaAwXlGVTS34KFe7W3/Rxd/pldTYKFOZsku2EzpISfH8Wsw==", - "dependencies": { - "@stablelib/chacha20poly1305": "1.0.1", - "@stablelib/hkdf": "1.0.1", - "@stablelib/random": "1.0.2", - "@stablelib/sha256": "1.0.1", - "@stablelib/x25519": "1.0.3", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.0.4", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.16.1", - "@walletconnect/window-getters": "1.0.1", - "@walletconnect/window-metadata": "1.0.1", - "detect-browser": "5.3.0", - "elliptic": "^6.5.7", - "query-string": "7.1.3", - "uint8arrays": "3.1.0" - } - }, - "node_modules/@web3modal/base/node_modules/base-x": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz", - "integrity": "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==", - "optional": true - }, - "node_modules/@web3modal/base/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" - }, - "node_modules/@web3modal/base/node_modules/bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", - "optional": true, - "dependencies": { - "base-x": "^4.0.0" - } - }, - "node_modules/@web3modal/base/node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/@web3modal/base/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@web3modal/common": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/common/-/common-5.1.11.tgz", - "integrity": "sha512-YfSklKjjiM1RGxFTQm3ycYZ2Ktb6vswt9eg8lGXRknxN+SC7bCtuvgtyyCO0Z9/f9dPMOGIAmoJ/y6WHXWQqcg==", - "dependencies": { - "bignumber.js": "9.1.2", - "dayjs": "1.11.10" - } - }, - "node_modules/@web3modal/common/node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" - }, - "node_modules/@web3modal/core": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/core/-/core-5.1.11.tgz", - "integrity": "sha512-ugUVFVml1vVW+V7yxkn/AYYdrUJzn4ulFbDlxDMpmukKY6sDYLMMGAJ84O8ZC/OPyC7009NYd3mKZurxEyWkHw==", - "deprecated": "Web3Modal is now Reown AppKit. Please follow the upgrade guide at https://docs.reown.com/appkit/upgrade/from-w3m-to-reown", - "dependencies": { - "@web3modal/common": "5.1.11", - "@web3modal/wallet": "5.1.11", - "valtio": "1.11.2" - } - }, - "node_modules/@web3modal/core/node_modules/proxy-compare": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz", - "integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==" - }, - "node_modules/@web3modal/core/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==", - "peer": true, + "node_modules/@xyflow/react": { + "version": "12.10.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz", + "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==", + "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0" + "@xyflow/system": "0.0.76", + "classcat": "^5.0.3", + "zustand": "^4.4.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@web3modal/core/node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": ">=17", + "react-dom": ">=17" } }, - "node_modules/@web3modal/core/node_modules/valtio": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.11.2.tgz", - "integrity": "sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==", + "node_modules/@xyflow/react/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", "dependencies": { - "proxy-compare": "2.5.1", - "use-sync-external-store": "1.2.0" + "use-sync-external-store": "^1.2.2" }, "engines": { - "node": ">=12.20.0" + "node": ">=12.7.0" }, "peerDependencies": { "@types/react": ">=16.8", + "immer": ">=9.0.6", "react": ">=16.8" }, "peerDependenciesMeta": { "@types/react": { "optional": true }, + "immer": { + "optional": true + }, "react": { "optional": true } } }, - "node_modules/@web3modal/polyfills": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/polyfills/-/polyfills-5.1.11.tgz", - "integrity": "sha512-BDIDYA2LGTCquahbZ+wyWQy4IBOPeKVSgt4ZpFir1fnVJUPkEluSwZStcKLtCzQvxJgER1sLicUrjJQHF36TOg==", - "dependencies": { - "buffer": "6.0.3" - } - }, - "node_modules/@web3modal/scaffold-ui": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/scaffold-ui/-/scaffold-ui-5.1.11.tgz", - "integrity": "sha512-fBqzd7DStUaEjtdbEU86rzY4XIgt8c8JN8oxS/xnUEopmjFYvBLCCVEfbTkZyJrRvAAphz7+oS4TVzXw9k6t5A==", - "dependencies": { - "@web3modal/common": "5.1.11", - "@web3modal/core": "5.1.11", - "@web3modal/scaffold-utils": "5.1.11", - "@web3modal/siwe": "5.1.11", - "@web3modal/ui": "5.1.11", - "@web3modal/wallet": "5.1.11", - "lit": "3.1.0" - } - }, - "node_modules/@web3modal/scaffold-ui/node_modules/lit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.0.tgz", - "integrity": "sha512-rzo/hmUqX8zmOdamDAeydfjsGXbbdtAFqMhmocnh2j9aDYqbu0fjXygjCa0T99Od9VQ/2itwaGrjZz/ZELVl7w==", + "node_modules/@xyflow/system": { + "version": "0.0.76", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz", + "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==", + "license": "MIT", "dependencies": { - "@lit/reactive-element": "^2.0.0", - "lit-element": "^4.0.0", - "lit-html": "^3.1.0" + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" } }, - "node_modules/@web3modal/scaffold-utils": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/scaffold-utils/-/scaffold-utils-5.1.11.tgz", - "integrity": "sha512-4bcYpQ3oxak5mDZMW5/7ayrhpaJHy6dCfUio15AGPHnQlFjkqcfSuuG0Io8Oj8VUXcK2UBLch9YiEDz4Xgce9Q==", - "dependencies": { - "@web3modal/common": "5.1.11", - "@web3modal/core": "5.1.11", - "@web3modal/polyfills": "5.1.11", - "@web3modal/wallet": "5.1.11", - "valtio": "1.11.2" - } - }, - "node_modules/@web3modal/scaffold-utils/node_modules/proxy-compare": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz", - "integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==" - }, - "node_modules/@web3modal/scaffold-utils/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==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@web3modal/scaffold-utils/node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@web3modal/scaffold-utils/node_modules/valtio": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.11.2.tgz", - "integrity": "sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==", - "dependencies": { - "proxy-compare": "2.5.1", - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@web3modal/siwe": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/siwe/-/siwe-5.1.11.tgz", - "integrity": "sha512-1aKEtMosACyY0SRjHjdcA/g3bRtMojTxlK7S/T6zBk57X/P3xcEZq9J8UM73plmGewjZdLaqGMgv6B/k/WleZQ==", - "deprecated": "Web3Modal is now Reown AppKit. Please follow the upgrade guide at https://docs.reown.com/appkit/upgrade/from-w3m-to-reown", - "dependencies": { - "@walletconnect/utils": "2.16.1", - "@web3modal/common": "5.1.11", - "@web3modal/core": "5.1.11", - "@web3modal/scaffold-utils": "5.1.11", - "@web3modal/ui": "5.1.11", - "@web3modal/wallet": "5.1.11", - "lit": "3.1.0", - "valtio": "1.11.2" - } - }, - "node_modules/@web3modal/siwe/node_modules/@walletconnect/relay-auth": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.0.4.tgz", - "integrity": "sha512-kKJcS6+WxYq5kshpPaxGHdwf5y98ZwbfuS4EE/NkQzqrDFm5Cj+dP8LofzWvjrrLkZq7Afy7WrQMXdLy8Sx7HQ==", - "dependencies": { - "@stablelib/ed25519": "^1.0.2", - "@stablelib/random": "^1.0.1", - "@walletconnect/safe-json": "^1.0.1", - "@walletconnect/time": "^1.0.2", - "tslib": "1.14.1", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/@web3modal/siwe/node_modules/@walletconnect/types": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.16.1.tgz", - "integrity": "sha512-9P4RG4VoDEF+yBF/n2TF12gsvT/aTaeZTVDb/AOayafqiPnmrQZMKmNCJJjq1sfdsDcHXFcZWMGsuCeSJCmrXA==", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "events": "3.3.0" - } - }, - "node_modules/@web3modal/siwe/node_modules/@walletconnect/utils": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.16.1.tgz", - "integrity": "sha512-aoQirVoDoiiEtYeYDtNtQxFzwO/oCrz9zqeEEXYJaAwXlGVTS34KFe7W3/Rxd/pldTYKFOZsku2EzpISfH8Wsw==", - "dependencies": { - "@stablelib/chacha20poly1305": "1.0.1", - "@stablelib/hkdf": "1.0.1", - "@stablelib/random": "1.0.2", - "@stablelib/sha256": "1.0.1", - "@stablelib/x25519": "1.0.3", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.0.4", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.16.1", - "@walletconnect/window-getters": "1.0.1", - "@walletconnect/window-metadata": "1.0.1", - "detect-browser": "5.3.0", - "elliptic": "^6.5.7", - "query-string": "7.1.3", - "uint8arrays": "3.1.0" - } - }, - "node_modules/@web3modal/siwe/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" - }, - "node_modules/@web3modal/siwe/node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/@web3modal/siwe/node_modules/lit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.0.tgz", - "integrity": "sha512-rzo/hmUqX8zmOdamDAeydfjsGXbbdtAFqMhmocnh2j9aDYqbu0fjXygjCa0T99Od9VQ/2itwaGrjZz/ZELVl7w==", - "dependencies": { - "@lit/reactive-element": "^2.0.0", - "lit-element": "^4.0.0", - "lit-html": "^3.1.0" - } - }, - "node_modules/@web3modal/siwe/node_modules/proxy-compare": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz", - "integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==" - }, - "node_modules/@web3modal/siwe/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==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@web3modal/siwe/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@web3modal/siwe/node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@web3modal/siwe/node_modules/valtio": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.11.2.tgz", - "integrity": "sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==", - "dependencies": { - "proxy-compare": "2.5.1", - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@web3modal/ui": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/ui/-/ui-5.1.11.tgz", - "integrity": "sha512-L0L+2YOK+ONx+W7GPtkSdKZuAQ8cjcS5N8kp+WZzKOMUTeDLuXKtSnES4p/ShOVmkpV6qB8r0pPA9xgFh1D3ow==", - "deprecated": "Web3Modal is now Reown AppKit. Please follow the upgrade guide at https://docs.reown.com/appkit/upgrade/from-w3m-to-reown", - "dependencies": { - "lit": "3.1.0", - "qrcode": "1.5.3" - } - }, - "node_modules/@web3modal/ui/node_modules/lit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.0.tgz", - "integrity": "sha512-rzo/hmUqX8zmOdamDAeydfjsGXbbdtAFqMhmocnh2j9aDYqbu0fjXygjCa0T99Od9VQ/2itwaGrjZz/ZELVl7w==", - "dependencies": { - "@lit/reactive-element": "^2.0.0", - "lit-element": "^4.0.0", - "lit-html": "^3.1.0" - } - }, - "node_modules/@web3modal/wagmi": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/wagmi/-/wagmi-5.1.11.tgz", - "integrity": "sha512-etV1qfBVvh41EMuBHXUpcO/W818jZVNh5/l9Z5kqRPZxlQmBaJbt5mTzw6nw/Lujoe1yYKugGQFhgjfEQK+eyA==", - "deprecated": "Web3Modal is now Reown AppKit. Please follow the upgrade guide at https://docs.reown.com/appkit/upgrade/from-w3m-to-reown", - "dependencies": { - "@walletconnect/ethereum-provider": "2.16.1", - "@walletconnect/utils": "2.16.1", - "@web3modal/base": "5.1.11", - "@web3modal/common": "5.1.11", - "@web3modal/polyfills": "5.1.11", - "@web3modal/scaffold-utils": "5.1.11", - "@web3modal/siwe": "5.1.11", - "@web3modal/wallet": "5.1.11" - }, - "peerDependencies": { - "@wagmi/connectors": ">=4", - "@wagmi/core": ">=2.0.0", - "react": ">=17", - "react-dom": ">=17", - "viem": ">=2.0.0", - "vue": ">=3", - "wagmi": ">=2.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/core": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.16.1.tgz", - "integrity": "sha512-UlsnEMT5wwFvmxEjX8s4oju7R3zadxNbZgsFeHEsjh7uknY2zgmUe1Lfc5XU6zyPb1Jx7Nqpdx1KN485ee8ogw==", - "dependencies": { - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/jsonrpc-ws-connection": "1.0.14", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.0.4", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.16.1", - "@walletconnect/utils": "2.16.1", - "events": "3.3.0", - "lodash.isequal": "4.5.0", - "uint8arrays": "3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/ethereum-provider": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.16.1.tgz", - "integrity": "sha512-oD7DNCssUX3plS5gGUZ9JQ63muQB/vxO68X6RzD2wd8gBsYtSPw4BqYFc7KTO6dUizD6gfPirw32yW2pTvy92w==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "dependencies": { - "@walletconnect/jsonrpc-http-connection": "1.0.8", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/modal": "2.6.2", - "@walletconnect/sign-client": "2.16.1", - "@walletconnect/types": "2.16.1", - "@walletconnect/universal-provider": "2.16.1", - "@walletconnect/utils": "2.16.1", - "events": "3.3.0" - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/jsonrpc-ws-connection": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.14.tgz", - "integrity": "sha512-Jsl6fC55AYcbkNVkwNM6Jo+ufsuCQRqViOQ8ZBPH9pRREHH9welbBiszuTLqEJiQcO/6XfFDl6bzCJIkrEi8XA==", - "dependencies": { - "@walletconnect/jsonrpc-utils": "^1.0.6", - "@walletconnect/safe-json": "^1.0.2", - "events": "^3.3.0", - "ws": "^7.5.1" - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/relay-auth": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.0.4.tgz", - "integrity": "sha512-kKJcS6+WxYq5kshpPaxGHdwf5y98ZwbfuS4EE/NkQzqrDFm5Cj+dP8LofzWvjrrLkZq7Afy7WrQMXdLy8Sx7HQ==", - "dependencies": { - "@stablelib/ed25519": "^1.0.2", - "@stablelib/random": "^1.0.1", - "@walletconnect/safe-json": "^1.0.1", - "@walletconnect/time": "^1.0.2", - "tslib": "1.14.1", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/sign-client": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.16.1.tgz", - "integrity": "sha512-s2Tx2n2duxt+sHtuWXrN9yZVaHaYqcEcjwlTD+55/vs5NUPlISf+fFmZLwSeX1kUlrSBrAuxPUcqQuRTKcjLOA==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "dependencies": { - "@walletconnect/core": "2.16.1", - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/logger": "2.1.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.16.1", - "@walletconnect/utils": "2.16.1", - "events": "3.3.0" - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/types": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.16.1.tgz", - "integrity": "sha512-9P4RG4VoDEF+yBF/n2TF12gsvT/aTaeZTVDb/AOayafqiPnmrQZMKmNCJJjq1sfdsDcHXFcZWMGsuCeSJCmrXA==", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "events": "3.3.0" - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/universal-provider": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.16.1.tgz", - "integrity": "sha512-q/tyWUVNenizuClEiaekx9FZj/STU1F3wpDK4PUIh3xh+OmUI5fw2dY3MaNDjyb5AyrS0M8BuQDeuoSuOR/Q7w==", - "dependencies": { - "@walletconnect/jsonrpc-http-connection": "1.0.8", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.16.1", - "@walletconnect/types": "2.16.1", - "@walletconnect/utils": "2.16.1", - "events": "3.3.0" - } - }, - "node_modules/@web3modal/wagmi/node_modules/@walletconnect/utils": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.16.1.tgz", - "integrity": "sha512-aoQirVoDoiiEtYeYDtNtQxFzwO/oCrz9zqeEEXYJaAwXlGVTS34KFe7W3/Rxd/pldTYKFOZsku2EzpISfH8Wsw==", - "dependencies": { - "@stablelib/chacha20poly1305": "1.0.1", - "@stablelib/hkdf": "1.0.1", - "@stablelib/random": "1.0.2", - "@stablelib/sha256": "1.0.1", - "@stablelib/x25519": "1.0.3", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.0.4", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.16.1", - "@walletconnect/window-getters": "1.0.1", - "@walletconnect/window-metadata": "1.0.1", - "detect-browser": "5.3.0", - "elliptic": "^6.5.7", - "query-string": "7.1.3", - "uint8arrays": "3.1.0" - } - }, - "node_modules/@web3modal/wagmi/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" - }, - "node_modules/@web3modal/wagmi/node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/@web3modal/wagmi/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@web3modal/wagmi/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@web3modal/wallet": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@web3modal/wallet/-/wallet-5.1.11.tgz", - "integrity": "sha512-/ooQZXK1h7LGBUemebldYPAV2oJAgxkgSiCMoHWynhuS0LO3BzhOhGL+jV19w4iU81bS1GSNFTxYT9LL6Scesw==", - "dependencies": { - "@walletconnect/logger": "2.1.2", - "@web3modal/common": "5.1.11", - "@web3modal/polyfills": "5.1.11", - "zod": "3.22.4" - } - }, - "node_modules/@web3modal/wallet/node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@xyflow/react": { - "version": "12.10.2", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz", - "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==", - "license": "MIT", - "dependencies": { - "@xyflow/system": "0.0.76", - "classcat": "^5.0.3", - "zustand": "^4.4.0" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@xyflow/react/node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@xyflow/system": { - "version": "0.0.76", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz", - "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==", - "license": "MIT", - "dependencies": { - "@types/d3-drag": "^3.0.7", - "@types/d3-interpolate": "^3.0.4", - "@types/d3-selection": "^3.0.10", - "@types/d3-transition": "^3.0.8", - "@types/d3-zoom": "^3.0.8", - "d3-drag": "^3.0.0", - "d3-interpolate": "^3.0.1", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" - } - }, - "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/abitype": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", - "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", + "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" }, @@ -13764,40 +12299,6 @@ } } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -13914,15 +12415,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -13972,18 +12464,6 @@ "node": "*" } }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/async-listen": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", @@ -14154,26 +12634,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==" }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dev": true, - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/borsh": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", @@ -14317,31 +12777,6 @@ "node": ">=6.14.2" } }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -14553,42 +12988,6 @@ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", "license": "MIT" }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -14624,12 +13023,6 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, - "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", - "dev": true - }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -14708,110 +13101,32 @@ "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-hrtime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-3.0.0.tgz", "integrity": "sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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 - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-es": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", - "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==" - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, + "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 + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/country-list": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/country-list/-/country-list-2.3.0.tgz", @@ -15061,15 +13376,6 @@ "node": ">=12" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/data-urls": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", @@ -15234,36 +13540,6 @@ "node": ">=0.10.0" } }, - "node_modules/default-browser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", - "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -15280,19 +13556,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", @@ -15317,15 +13580,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -15367,15 +13621,6 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, - "node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -15407,17 +13652,6 @@ "@types/trusted-types": "^2.0.7" } }, - "node_modules/dotenv": { - "version": "17.2.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", - "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -15574,12 +13808,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, "node_modules/electron-to-chromium": { "version": "1.5.234", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz", @@ -15615,15 +13843,6 @@ "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -15703,9 +13922,9 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -15715,30 +13934,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -15852,12 +14047,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -15996,19 +14185,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -16240,143 +14416,6 @@ "node": ">=0.8.x" } }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "dev": true, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.1", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "dev": true, - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "dev": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/express/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/extension-port-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", @@ -16397,6 +14436,16 @@ "node": "> 0.1.90" } }, + "node_modules/fake-indexeddb": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz", + "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -16475,44 +14524,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "dev": true, - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -16550,23 +14561,6 @@ "node": ">=0.10.0" } }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dev": true, - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -16661,18 +14655,6 @@ "node": ">= 6" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/formik": { "version": "2.2.9", "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz", @@ -16701,15 +14683,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/framer-motion": { "version": "12.38.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", @@ -16737,35 +14710,13 @@ } } }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "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" @@ -16782,18 +14733,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fuzzysort": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", - "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", - "dev": true - }, - "node_modules/fzf": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fzf/-/fzf-0.5.2.tgz", - "integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -16805,22 +14744,10 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "dev": true, + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-func-name": { @@ -16863,18 +14790,6 @@ "node": ">=6" } }, - "node_modules/get-own-enumerable-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", - "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -16887,34 +14802,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-stream/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-tsconfig": { "version": "4.13.6", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", @@ -16969,9 +14856,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16982,13 +14869,13 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -17075,16 +14962,16 @@ } }, "node_modules/happy-dom": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.3.3.tgz", - "integrity": "sha512-hM9gltmtQLfmWPqoPreUtRdP3nZCSzQEw7l/JC+up5CxquDykhYFKzIzoFFeVev3AGFEULNvsbE8fpZPgxUYEQ==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.9.0.tgz", + "integrity": "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", - "entities": "^4.5.0", + "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" }, @@ -17167,17 +15054,6 @@ "node": ">= 0.4" } }, - "node_modules/headers-polyfill": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", - "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", - "dev": true - }, - "node_modules/hey-listen": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", - "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" - }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -17232,31 +15108,6 @@ "void-elements": "3.1.0" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -17284,15 +15135,6 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, - "engines": { - "node": ">=18.18.0" - } - }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -17359,6 +15201,22 @@ } ] }, + "node_modules/iframe-shared-storage": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/iframe-shared-storage/-/iframe-shared-storage-1.0.34.tgz", + "integrity": "sha512-sb80NhdVgVNhkc72pTyAjcaHQNiIPeu314zFtXbQaTjp9OOnaZ5+IjS/ylNXs08kwvlmBpg7GG5GOWzjRMP9Ww==", + "license": "ISC", + "dependencies": { + "idb-keyval": "^6.2.2", + "postmsg-rpc": "^2.4.0" + } + }, + "node_modules/iframe-shared-storage/node_modules/idb-keyval": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "license": "Apache-2.0" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -17368,6 +15226,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -17417,15 +15285,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -17465,22 +15324,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -17527,56 +15370,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-ssh": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", - "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "dev": true - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -17586,30 +15379,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", - "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -17617,12 +15386,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -17640,18 +15403,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regexp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -17677,34 +15428,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -17943,12 +15666,6 @@ "react": ">=16.0.0" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, "node_modules/json-rpc-engine": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", @@ -18051,15 +15768,6 @@ "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==" }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -18216,6 +15924,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -18237,6 +15948,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -18334,12 +16048,6 @@ "url": "https://opencollective.com/parcel" } }, - "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 - }, "node_modules/lit": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", @@ -18409,58 +16117,12 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "dev": true, - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -18525,7 +16187,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -18556,27 +16217,6 @@ "@babel/runtime": "^7.12.5" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -18629,27 +16269,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -18682,15 +16301,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -18774,7 +16384,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -18785,24 +16394,10 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, - "node_modules/motion": { - "version": "10.16.2", - "resolved": "https://registry.npmjs.org/motion/-/motion-10.16.2.tgz", - "integrity": "sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==", - "dependencies": { - "@motionone/animation": "^10.15.1", - "@motionone/dom": "^10.16.2", - "@motionone/svelte": "^10.16.2", - "@motionone/types": "^10.15.1", - "@motionone/utils": "^10.15.1", - "@motionone/vue": "^10.16.2" - } - }, "node_modules/motion-dom": { "version": "12.38.0", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", @@ -18833,140 +16428,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/msw": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.11.5.tgz", - "integrity": "sha512-atFI4GjKSJComxcigz273honh8h4j5zzpk5kwG4tGm0TPcYne6bqmVrufeRll6auBeouIkXqZYXxVbWSWxM3RA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.39.1", - "@open-draft/deferred-promise": "^2.2.0", - "@types/statuses": "^2.0.4", - "cookie": "^1.0.2", - "graphql": "^16.8.1", - "headers-polyfill": "^4.0.2", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "path-to-regexp": "^6.3.0", - "picocolors": "^1.1.1", - "rettime": "^0.7.0", - "statuses": "^2.0.2", - "strict-event-emitter": "^0.5.1", - "tough-cookie": "^6.0.0", - "type-fest": "^4.26.1", - "until-async": "^3.0.2", - "yargs": "^17.7.2" - }, - "bin": { - "msw": "cli/index.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mswjs" - }, - "peerDependencies": { - "typescript": ">= 4.8.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/msw/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/msw/node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/msw/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/msw/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/msw/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/msw/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/multiformats": { "version": "9.9.0", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" }, - "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/nanoclone": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", @@ -18976,7 +16442,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -18996,15 +16461,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", @@ -19020,26 +16476,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -19085,6 +16521,12 @@ "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", "dev": true }, + "node_modules/node-tfhe": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/node-tfhe/-/node-tfhe-0.11.1.tgz", + "integrity": "sha512-QFkIeIZ5IbdvhpkzImwakL2+sZFCTXCwGvOacE1NczR+zgrg5ixHU9KpjuuIi1YbbKFGJEMoSUmivDktuzFaKg==", + "license": "BSD-3-Clause-Clear" + }, "node_modules/nopt": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", @@ -19109,34 +16551,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/obj-multiplex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/obj-multiplex/-/obj-multiplex-1.0.0.tgz", @@ -19187,18 +16601,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-property-assigner": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/object-property-assigner/-/object-property-assigner-1.3.5.tgz", @@ -19209,15 +16611,6 @@ "resolved": "https://registry.npmjs.org/object-property-extractor/-/object-property-extractor-1.0.13.tgz", "integrity": "sha512-9kgEjTWDhTPuPn7nyof+5mLmCKBPKdU0c7IVpTbOvYKYSdXQ5skH4Pa/8MPbZXeyXBGrqS82JyWecsh6tMxiLw==" }, - "node_modules/object-treeify": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", - "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/ofetch": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", @@ -19228,33 +16621,11 @@ "ufo": "^1.5.4" } }, - "node_modules/ohm-js": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/ohm-js/-/ohm-js-17.5.0.tgz", - "integrity": "sha512-l4Sa7026+6jsvYbt0PXKmL+f+ML32fD++IznLgxDhx2t9Cx6NC7zwRqblCujPHGGmkQerHoeBzRutdxaw/S72g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.1" - } - }, "node_modules/on-exit-leak-free": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", "integrity": "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==" }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -19263,42 +16634,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", - "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/openapi-fetch": { "version": "0.13.8", "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.13.8.tgz", @@ -19329,97 +16664,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "dev": true, - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true - }, - "node_modules/ora/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "dev": true - }, "node_modules/ox": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", @@ -19531,57 +16775,21 @@ "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-manager-detector": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.4.0.tgz", - "integrity": "sha512-rRZ+pR1Usc+ND9M2NkmCvE/LYJS+8ORVV9X0KuNSY/gFsp7RBHJM/ADh9LYq4Vvfq6QkKrW6/weuh8SMEtN5gw==", - "dev": true - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "engines": { - "node": ">=18" + "dependencies": { + "callsites": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6" } }, "node_modules/parse5": { @@ -19610,15 +16818,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -19660,21 +16859,15 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true - }, "node_modules/path-to-regexp-updated": { "name": "path-to-regexp", "version": "6.3.0", @@ -19757,25 +16950,6 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" }, - "node_modules/pip-requirements-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pip-requirements-js/-/pip-requirements-js-1.0.2.tgz", - "integrity": "sha512-awqoNOSOl4Blu4E4Hzp7jL0g8WKEhCwO+s7C2ibtIW3CAJMwspgoTXd4vnHd21UmhdrsI44Pn8FFSuA8QKrzvg==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "ohm-js": "^17.1.0" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "dev": true, - "engines": { - "node": ">=16.20.0" - } - }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", @@ -19850,31 +17024,13 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, + "node_modules/postmsg-rpc": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/postmsg-rpc/-/postmsg-rpc-2.4.0.tgz", + "integrity": "sha512-adGH2zGSxhCUOfUfAXdRn4tgZVWauaSP2X8on+g7uBA45sxkzORL1oia95eXZtcZk5Sp4JTZmDFOTe+D24avBQ==", "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/powershell-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", - "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "shortid": "^2.2.8" } }, "node_modules/preact": { @@ -19927,21 +17083,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, - "node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "dev": true, - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -19952,28 +17093,6 @@ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -19989,19 +17108,6 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-compare": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", @@ -20055,21 +17161,6 @@ "node": ">=10.13.0" } }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -20281,46 +17372,6 @@ "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==" }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/react": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", @@ -20590,22 +17641,6 @@ "node": ">= 12.13.0" } }, - "node_modules/recast": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", - "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", - "dev": true, - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -20662,28 +17697,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rettime": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", - "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", - "dev": true - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -20734,32 +17747,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/rpc-websockets": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.1.3.tgz", @@ -20797,19 +17784,6 @@ "dev": true, "license": "MIT" }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -20914,64 +17888,6 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dev": true, - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "dev": true, - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -20981,136 +17897,41 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" - }, - "bin": { - "sha.js": "bin.js" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sha256-uint8array": { - "version": "0.10.7", - "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.7.tgz", - "integrity": "sha512-1Q6JQU4tX9NqsDGodej6pkrUVQVNapLZnvkwIhddH/JqzBZF1fSaxSWNY6sziXBE8aEa2twtGkXUrwzGeZCMpQ==" - }, - "node_modules/shadcn": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-3.6.2.tgz", - "integrity": "sha512-2g48/7UsXTSWMFU9GYww85AN5iVTkErbeycrcleI55R+atqW8HE1M/YDFyQ+0T3Bwsd4e8vycPu9gmwODunDpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/ni": "^25.0.0", - "@babel/core": "^7.28.0", - "@babel/parser": "^7.28.0", - "@babel/plugin-transform-typescript": "^7.28.0", - "@babel/preset-typescript": "^7.27.1", - "@dotenvx/dotenvx": "^1.48.4", - "@modelcontextprotocol/sdk": "^1.17.2", - "browserslist": "^4.26.2", - "commander": "^14.0.0", - "cosmiconfig": "^9.0.0", - "dedent": "^1.6.0", - "deepmerge": "^4.3.1", - "diff": "^8.0.2", - "execa": "^9.6.0", - "fast-glob": "^3.3.3", - "fs-extra": "^11.3.1", - "fuzzysort": "^3.1.0", - "https-proxy-agent": "^7.0.6", - "kleur": "^4.1.5", - "msw": "^2.10.4", - "node-fetch": "^3.3.2", - "open": "^11.0.0", - "ora": "^8.2.0", - "postcss": "^8.5.6", - "postcss-selector-parser": "^7.1.0", - "prompts": "^2.4.2", - "recast": "^0.23.11", - "stringify-object": "^5.0.0", - "ts-morph": "^26.0.0", - "tsconfig-paths": "^4.2.0", - "zod": "^3.24.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "shadcn": "dist/index.js" - } - }, - "node_modules/shadcn/node_modules/commander": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", - "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", - "dev": true, - "engines": { - "node": ">=20" - } - }, - "node_modules/shadcn/node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/shadcn/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shadcn/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "dev": true, - "peerDependencies": { - "zod": "^3.24.1" - } + "node_modules/sha256-uint8array": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.7.tgz", + "integrity": "sha512-1Q6JQU4tX9NqsDGodej6pkrUVQVNapLZnvkwIhddH/JqzBZF1fSaxSWNY6sziXBE8aEa2twtGkXUrwzGeZCMpQ==" }, "node_modules/shallowequal": { "version": "1.1.0", @@ -21188,76 +18009,13 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, + "node_modules/shortid": { + "version": "2.2.17", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.17.tgz", + "integrity": "sha512-GpbM3gLF1UUXZvQw6MCyulHkWbRseNO4cyBEZresZRorwl1+SLu1ZdqgVtuwqz8mB6RpwPkm541mYSqrKyJSaA==", + "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "nanoid": "^3.3.8" } }, "node_modules/siginfo": { @@ -21286,12 +18044,6 @@ "is-arrayish": "^0.3.1" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, "node_modules/smol-toml": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.2.tgz", @@ -21381,15 +18133,6 @@ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -21427,33 +18170,12 @@ "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", "license": "MIT" }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/std-env": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", "dev": true }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -21472,12 +18194,6 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true - }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -21507,23 +18223,6 @@ "node": ">=8" } }, - "node_modules/stringify-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", - "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", - "dev": true, - "dependencies": { - "get-own-enumerable-keys": "^1.0.0", - "is-obj": "^3.0.0", - "is-regexp": "^3.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/stringify-object?sponsor=1" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -21535,27 +18234,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -21658,9 +18336,9 @@ } }, "node_modules/tar": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", - "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -21695,6 +18373,12 @@ "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" }, + "node_modules/tfhe": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/tfhe/-/tfhe-0.11.1.tgz", + "integrity": "sha512-D4YeL0DYz4IOV/X7YzKFexkjbJdOdGn2M7mfDNOQW1OxA+NO/Z61BJ6ULLTvdBEQ3N6P985lNO61Drgsjf0g/w==", + "license": "BSD-3-Clause-Clear" + }, "node_modules/thread-stream": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz", @@ -21719,12 +18403,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true - }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -21736,12 +18414,6 @@ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true }, - "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", - "dev": true - }, "node_modules/tinypool": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", @@ -21801,15 +18473,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", @@ -21862,16 +18525,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-morph": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", - "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", - "dev": true, - "dependencies": { - "@ts-morph/common": "~0.27.0", - "code-block-writer": "^13.0.3" - } - }, "node_modules/ts-toolbelt": { "version": "6.15.5", "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", @@ -21879,20 +18532,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -22412,6 +19051,12 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -22433,53 +19078,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -22589,18 +19187,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -22610,15 +19196,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unstorage": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.1.tgz", @@ -22719,15 +19296,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, - "node_modules/until-async": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", - "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/kettanaito" - } - }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -22891,33 +19459,25 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/viem": { - "version": "2.37.3", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.37.3.tgz", - "integrity": "sha512-hwoZqkFSy13GCFzIftgfIH8hNENvdlcHIvtLt73w91tL6rKmZjQisXWTahi1Vn5of8/JQ1FBKfwUus3YkDXwbw==", + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.48.1.tgz", + "integrity": "sha512-GJC3gKV1Hngeo1IB9YanJKHH2pcmoqDymyPxddmzDtG8boXA7eFw8qdnn1PSaToJ93f3LpOZPlLLJ9beAF/Lzg==", "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.1.0", + "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.9.3", + "ox": "0.14.17", "ws": "8.18.3" }, "peerDependencies": { @@ -22933,6 +19493,7 @@ "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" }, @@ -22944,6 +19505,7 @@ "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" }, @@ -22958,6 +19520,7 @@ "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" }, @@ -22969,6 +19532,7 @@ "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", @@ -22982,6 +19546,7 @@ "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" @@ -22991,15 +19556,16 @@ } }, "node_modules/viem/node_modules/ox": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.3.tgz", - "integrity": "sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==", + "version": "0.14.17", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.17.tgz", + "integrity": "sha512-jOzNb2Wlfzsr8z/GoCtd1bf6OSRuWuysvbhnHGD+7fV1WRbcBR6B0RYoe3xWnUedF7zp4l5APmS7CzAhUok/lA==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/wevm" } ], + "license": "MIT", "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", @@ -23007,7 +19573,7 @@ "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", - "abitype": "^1.0.9", + "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { @@ -23332,15 +19898,6 @@ } } }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/webextension-polyfill": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", @@ -23488,23 +20045,6 @@ } } }, - "node_modules/wsl-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.0.tgz", - "integrity": "sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -23642,30 +20182,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yup": { "version": "0.32.11", "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", diff --git a/package.json b/package.json index e4f3c3b..e41aa6d 100644 --- a/package.json +++ b/package.json @@ -19,17 +19,15 @@ "check:inline-copy": "node scripts/check-inline-copy.mjs" }, "dependencies": { + "@cofhe/sdk": "^0.4.0", "@dynamic-labs/ethereum": "^4.30.3", "@dynamic-labs/sdk-react-core": "^4.30.3", "@dynamic-labs/wagmi-connector": "^4.30.3", "@monaco-editor/react": "^4.7.0", "@phosphor-icons/react": "^2.1.10", - "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-hover-card": "^1.1.15", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-radio-group": "^1.2.3", @@ -43,9 +41,7 @@ "@shazow/whatsabi": "^0.22.2", "@solidity-parser/parser": "^0.20.2", "@tanstack/react-query": "^5.87.4", - "@types/react-router-dom": "^5.3.3", - "@web3modal/siwe": "^5.1.11", - "@web3modal/wagmi": "^5.1.11", + "@wagmi/core": "^2.20.3", "@xyflow/react": "^12.10.2", "animejs": "^4.3.5", "axios": "^1.11.0", @@ -53,11 +49,12 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "dompurify": "^3.3.1", - "dotenv": "^17.2.2", "ethers": "^5.7.2", "framer-motion": "^12.38.0", + "iframe-shared-storage": "^1.0.34", "json-edit-react": "^1.28.2", "lucide-react": "^0.542.0", + "monaco-editor": "^0.55.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "^19.1.1", @@ -68,6 +65,7 @@ "react-window": "^2.2.3", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", + "tfhe": "^0.11.1", "viem": "^2.37.3", "wagmi": "^2.16.9", "ws": "^8.18.3", @@ -79,20 +77,17 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/dompurify": "^3.0.5", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", - "@types/react-window": "^1.8.8", - "@vercel/node": "^5.6.9", + "@vercel/node": "^5.7.6", "@vitejs/plugin-react": "^4.7.0", - "baseline-browser-mapping": "^2.10.0", "eslint": "^9.33.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", + "fake-indexeddb": "^6.2.5", "globals": "^16.3.0", - "happy-dom": "^20.3.3", + "happy-dom": "^20.9.0", "jsdom": "^27.0.1", - "shadcn": "^3.6.2", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", "typescript": "~5.8.3", diff --git a/scripts/bridge-config.mjs b/scripts/bridge-config.mjs index 5faac78..3aa525e 100644 --- a/scripts/bridge-config.mjs +++ b/scripts/bridge-config.mjs @@ -228,3 +228,92 @@ export class SimulationSemaphore { } } } + +// ============================================================================= +// Heimdall CLI configuration +// ============================================================================= + +export const HEIMDALL_BIN_PATH = process.env.HEIMDALL_BIN_PATH || "heimdall"; +export const HEIMDALL_DECOMPILE_TIMEOUT_MS = Number.parseInt( + process.env.HEIMDALL_DECOMPILE_TIMEOUT_MS || "60000", + 10, +); +export const HEIMDALL_DUMP_TIMEOUT_MS = Number.parseInt( + process.env.HEIMDALL_DUMP_TIMEOUT_MS || "180000", + 10, +); +export const HEIMDALL_CACHE_MAX_ENTRIES = Number.parseInt( + process.env.HEIMDALL_CACHE_MAX_ENTRIES || "256", + 10, +); +export const HEIMDALL_CACHE_TTL_MS = Number.parseInt( + process.env.HEIMDALL_CACHE_TTL_MS || String(60 * 60 * 1000), + 10, +); +export const HEIMDALL_CONCURRENCY = Number.parseInt( + process.env.HEIMDALL_CONCURRENCY || "2", + 10, +); + +// Server-side RPC allowlist, keyed by chainId. The bridge NEVER accepts a raw +// rpcUrl from the client (SSRF risk: internal hosts, cloud metadata endpoints, +// private RFC-1918 nets). Clients pass chainId; server resolves here. +// Override per-deployment with HEIMDALL_RPC_BY_CHAIN (JSON). +const DEFAULT_RPC_BY_CHAIN = { + 1: "https://cloudflare-eth.com", + 10: "https://mainnet.optimism.io", + 8453: "https://mainnet.base.org", + 42161: "https://arb1.arbitrum.io/rpc", +}; +function parseRpcByChain() { + const raw = process.env.HEIMDALL_RPC_BY_CHAIN; + if (!raw) return { ...DEFAULT_RPC_BY_CHAIN }; + try { + const parsed = JSON.parse(raw); + if (!parsed || typeof parsed !== "object") return { ...DEFAULT_RPC_BY_CHAIN }; + const out = { ...DEFAULT_RPC_BY_CHAIN }; + for (const [k, v] of Object.entries(parsed)) { + const chain = Number.parseInt(k, 10); + if (!Number.isInteger(chain) || chain <= 0) continue; + if (typeof v !== "string") continue; + out[chain] = v; + } + return out; + } catch { + return { ...DEFAULT_RPC_BY_CHAIN }; + } +} +export const HEIMDALL_RPC_BY_CHAIN = parseRpcByChain(); + +// Defense in depth on top of the chain-id allowlist — reject URLs that point +// at loopback, link-local, RFC-1918 private nets, or cloud metadata endpoints. +function isPrivateOrInternalHost(hostname) { + const h = hostname.toLowerCase(); + if (h === "localhost" || h.endsWith(".localhost")) return true; + if (h === "0.0.0.0" || h === "::" || h === "::1") return true; + if (h === "169.254.169.254") return true; + if (h === "metadata.google.internal") return true; + if (/^127\./.test(h)) return true; + if (/^10\./.test(h)) return true; + if (/^192\.168\./.test(h)) return true; + if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(h)) return true; + if (/^fe80:/i.test(h)) return true; + return false; +} + +export function resolveHeimdallRpcUrl(chainId) { + const url = HEIMDALL_RPC_BY_CHAIN[chainId]; + if (!url) return { ok: false, reason: "chain_not_allowlisted" }; + try { + const u = new URL(url); + if (u.protocol !== "http:" && u.protocol !== "https:") { + return { ok: false, reason: "bad_rpc_scheme" }; + } + if (isPrivateOrInternalHost(u.hostname)) { + return { ok: false, reason: "rpc_host_blocked" }; + } + return { ok: true, url }; + } catch { + return { ok: false, reason: "bad_rpc_url" }; + } +} diff --git a/scripts/heimdall-cache.mjs b/scripts/heimdall-cache.mjs new file mode 100644 index 0000000..902bed2 --- /dev/null +++ b/scripts/heimdall-cache.mjs @@ -0,0 +1,57 @@ +// scripts/heimdall-cache.mjs +// +// Simple LRU cache with TTL eviction. Keys are strings; values are arbitrary. +// Map preserves insertion order; reads promote via delete+reinsert. Expired +// entries are lazily purged on read. + +export function createLruCache({ maxEntries, ttlMs }) { + if (!Number.isFinite(maxEntries) || maxEntries <= 0) { + throw new Error("createLruCache: maxEntries must be positive"); + } + if (!Number.isFinite(ttlMs) || ttlMs <= 0) { + throw new Error("createLruCache: ttlMs must be positive"); + } + + const entries = new Map(); + const isExpired = (entry) => Date.now() - entry.storedAt > ttlMs; + + function purgeExpired() { + for (const [key, entry] of entries) { + if (isExpired(entry)) entries.delete(key); + } + } + + function evictIfOverCapacity() { + while (entries.size > maxEntries) { + const oldestKey = entries.keys().next().value; + if (oldestKey === undefined) break; + entries.delete(oldestKey); + } + } + + return { + get(key) { + const entry = entries.get(key); + if (!entry) return undefined; + if (isExpired(entry)) { + entries.delete(key); + return undefined; + } + entries.delete(key); + entries.set(key, entry); + return entry.value; + }, + set(key, value) { + if (entries.has(key)) entries.delete(key); + entries.set(key, { value, storedAt: Date.now() }); + evictIfOverCapacity(); + }, + size() { + purgeExpired(); + return entries.size; + }, + clear() { + entries.clear(); + }, + }; +} diff --git a/scripts/heimdall-manager.mjs b/scripts/heimdall-manager.mjs new file mode 100644 index 0000000..8b69645 --- /dev/null +++ b/scripts/heimdall-manager.mjs @@ -0,0 +1,363 @@ +// scripts/heimdall-manager.mjs +// +// Bridge endpoints for Heimdall decompilation and storage dump. +// Dispatches POST /heimdall/{version,decompile,dump}. + +import { createHash } from "node:crypto"; +import { + HEIMDALL_BIN_PATH, + HEIMDALL_DECOMPILE_TIMEOUT_MS, + HEIMDALL_DUMP_TIMEOUT_MS, + HEIMDALL_CACHE_MAX_ENTRIES, + HEIMDALL_CACHE_TTL_MS, + HEIMDALL_CONCURRENCY, + resolveHeimdallRpcUrl, +} from "./bridge-config.mjs"; +import { runHeimdallSubprocess, HeimdallRunError } from "./heimdall-runner.mjs"; +import { createLruCache } from "./heimdall-cache.mjs"; + +const resultCache = createLruCache({ + maxEntries: HEIMDALL_CACHE_MAX_ENTRIES, + ttlMs: HEIMDALL_CACHE_TTL_MS, +}); + +export function _clearHeimdallCache() { resultCache.clear(); } + +let activeHeimdall = 0; +const heimdallWaiters = []; +function acquireHeimdallSlot() { + if (activeHeimdall < HEIMDALL_CONCURRENCY) { + activeHeimdall++; + return Promise.resolve(); + } + return new Promise((resolve) => { heimdallWaiters.push(resolve); }); +} +function releaseHeimdallSlot() { + const next = heimdallWaiters.shift(); + if (next) next(); + else activeHeimdall--; +} +async function withHeimdallSlot(fn) { + await acquireHeimdallSlot(); + try { return await fn(); } + finally { releaseHeimdallSlot(); } +} + +let cachedHeimdallVersion = null; + +function isHexByteString(s) { + return typeof s === "string" && /^0x[0-9a-fA-F]*$/.test(s) && s.length >= 4; +} +function isAddress(s) { + return typeof s === "string" && /^0x[0-9a-fA-F]{40}$/.test(s); +} + +function sha256Hex(bytes) { + const h = createHash("sha256"); + h.update(bytes); + return "0x" + h.digest("hex"); +} + +async function fetchCodeHash(rpcUrl, address) { + try { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 10_000); + const resp = await fetch(rpcUrl, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "eth_getCode", + params: [address, "latest"], + }), + signal: controller.signal, + }); + clearTimeout(timer); + if (!resp.ok) return { ok: false, reason: `rpc_http_${resp.status}` }; + const payload = await resp.json(); + if (payload?.error) return { ok: false, reason: `rpc_error:${payload.error.message ?? "unknown"}` }; + const code = payload?.result; + if (typeof code !== "string" || !/^0x[0-9a-fA-F]*$/.test(code)) { + return { ok: false, reason: "rpc_bad_result" }; + } + if (code === "0x" || code.length <= 2) { + return { ok: false, reason: "eoa_or_empty_code" }; + } + return { ok: true, hash: sha256Hex(Buffer.from(code.slice(2), "hex")) }; + } catch (err) { + return { ok: false, reason: err?.name === "AbortError" ? "rpc_timeout" : "rpc_fetch_failed" }; + } +} + +function sendError(res, status, code, message, details) { + res.writeHead(status, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: code, message, details })); +} +function sendOk(res, body) { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify(body)); +} + +async function resolveHeimdallVersion() { + if (cachedHeimdallVersion) return cachedHeimdallVersion; + try { + const result = await runHeimdallSubprocess({ + bin: HEIMDALL_BIN_PATH, + args: ["--version"], + timeoutMs: 5000, + }); + const match = result.stdout.match(/\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)\b/); + cachedHeimdallVersion = match?.[1] ?? "unknown"; + } catch { + cachedHeimdallVersion = "unknown"; + } + return cachedHeimdallVersion; +} + +function classifyRunError(err) { + if (err instanceof HeimdallRunError || err?.code) { + switch (err.code) { + case "HEIMDALL_NOT_INSTALLED": return { status: 503, code: "heimdall_not_installed" }; + case "HEIMDALL_TIMEOUT": return { status: 504, code: "heimdall_timeout" }; + case "HEIMDALL_SPAWN_FAILED": return { status: 500, code: "heimdall_crash" }; + default: return { status: 500, code: "heimdall_upstream_error" }; + } + } + return { status: 500, code: "heimdall_upstream_error" }; +} + +async function handleVersion(res) { + try { + const v = await resolveHeimdallVersion(); + sendOk(res, { available: v !== "unknown", version: v !== "unknown" ? v : undefined }); + } catch { + sendOk(res, { available: false }); + } +} + +async function handleDecompile(body, res) { + const { bytecode, address, chainId, rpcUrl: rejectedRpcUrl } = body ?? {}; + + if (rejectedRpcUrl !== undefined) { + return sendError( + res, + 400, + "bad_request", + "rpcUrl is not accepted from the client; pass chainId and the server will resolve RPC", + ); + } + + let cacheKey; + let args; + let resolvedHash; + + if (bytecode !== undefined) { + if (!isHexByteString(bytecode)) { + return sendError(res, 400, "bad_request", "bytecode must be 0x-prefixed hex"); + } + resolvedHash = sha256Hex(Buffer.from(bytecode.slice(2), "hex")); + cacheKey = `decompile:${resolvedHash}`; + args = ["decompile", bytecode, "--output", "json", "--skip-resolving", "--no-tui"]; + } else if (address !== undefined) { + if (!isAddress(address)) { + return sendError(res, 400, "bad_request", "address must be 0x-prefixed 20-byte hex"); + } + if (!Number.isInteger(chainId) || chainId <= 0) { + return sendError(res, 400, "bad_request", "chainId must be a positive integer"); + } + const resolved = resolveHeimdallRpcUrl(chainId); + if (!resolved.ok) { + return sendError(res, 400, "bad_request", `chain ${chainId} not allowlisted`, { reason: resolved.reason }); + } + const fetched = await fetchCodeHash(resolved.url, address); + if (!fetched.ok) { + return sendError(res, 502, "heimdall_rpc_failed", fetched.reason); + } + resolvedHash = fetched.hash; + cacheKey = `decompile:${chainId}:${address.toLowerCase()}:${resolvedHash}`; + args = ["decompile", address, "--rpc-url", resolved.url, "--output", "json", "--skip-resolving", "--no-tui"]; + } else { + return sendError(res, 400, "bad_request", "Provide either bytecode or (address + chainId)"); + } + + const cached = resultCache.get(cacheKey); + if (cached) { + return sendOk(res, { ...cached, cacheHit: true }); + } + + const heimdallVersion = await resolveHeimdallVersion(); + + let result; + try { + result = await withHeimdallSlot(() => runHeimdallSubprocess({ + bin: HEIMDALL_BIN_PATH, + args, + timeoutMs: HEIMDALL_DECOMPILE_TIMEOUT_MS, + })); + } catch (err) { + const { status, code } = classifyRunError(err); + return sendError(res, status, code, err?.message, err?.stderr); + } + + if (result.exitCode !== 0) { + return sendError(res, 502, "heimdall_upstream_error", `heimdall exit ${result.exitCode}`, result.stderr); + } + + let parsed; + try { + parsed = JSON.parse(result.stdout); + } catch { + return sendError(res, 502, "heimdall_invalid_output", "heimdall did not return JSON", result.stdout.slice(0, 500)); + } + + if (typeof parsed.source !== "string" || !Array.isArray(parsed.abi)) { + return sendError(res, 502, "heimdall_invalid_output", "heimdall JSON missing source/abi", JSON.stringify(parsed).slice(0, 500)); + } + + const response = { + source: parsed.source, + abi: parsed.abi, + bytecodeHash: resolvedHash, + heimdallVersion, + cacheHit: false, + generatedAt: Date.now(), + }; + + resultCache.set(cacheKey, response); + sendOk(res, response); +} + +function normalizeBlockTag(input) { + if (input === undefined || input === null) return { tag: "latest", cacheTag: "latest", cacheable: false }; + if (typeof input === "number" && Number.isFinite(input) && input >= 0) { + return { tag: String(input), cacheTag: String(input), cacheable: true }; + } + if (typeof input === "string") { + if (/^(latest|pending)$/i.test(input)) { + return { tag: input.toLowerCase(), cacheTag: input.toLowerCase(), cacheable: false }; + } + if (/^earliest$/i.test(input)) { + return { tag: "earliest", cacheTag: "earliest", cacheable: true }; + } + if (/^\d+$/.test(input) || /^0x[0-9a-fA-F]+$/.test(input)) { + return { tag: input, cacheTag: input.toLowerCase(), cacheable: true }; + } + } + return null; +} + +function normalizeDumpSlots(raw) { + if (Array.isArray(raw)) { + return raw.map((e) => ({ + slot: String(e.slot), + value: String(e.value), + modifiers: Array.isArray(e.modifiers) ? e.modifiers : undefined, + })); + } + if (raw && typeof raw === "object") { + return Object.entries(raw).map(([slot, v]) => ({ + slot, + value: typeof v === "string" ? v : String(v.value), + modifiers: v && Array.isArray(v.modifiers) ? v.modifiers : undefined, + })); + } + return null; +} + +async function handleDump(body, res) { + const { address, chainId, rpcUrl: rejectedRpcUrl, blockNumber, blockTag } = body ?? {}; + + if (rejectedRpcUrl !== undefined) { + return sendError( + res, + 400, + "bad_request", + "rpcUrl is not accepted from the client; pass chainId and the server will resolve RPC", + ); + } + + if (!isAddress(address)) { + return sendError(res, 400, "bad_request", "address must be 20-byte 0x-prefixed hex"); + } + if (!Number.isInteger(chainId) || chainId <= 0) { + return sendError(res, 400, "bad_request", "chainId must be a positive integer"); + } + const resolved = resolveHeimdallRpcUrl(chainId); + if (!resolved.ok) { + return sendError(res, 400, "bad_request", `chain ${chainId} not allowlisted`, { reason: resolved.reason }); + } + + const blockInput = blockNumber !== undefined ? blockNumber : blockTag; + const block = normalizeBlockTag(blockInput); + if (!block) { + return sendError(res, 400, "bad_request", "blockNumber/blockTag invalid"); + } + + const normalizedAddress = address.toLowerCase(); + const cacheKey = block.cacheable + ? `dump:${chainId}:${normalizedAddress}:${block.cacheTag}` + : null; + if (cacheKey) { + const cached = resultCache.get(cacheKey); + if (cached) return sendOk(res, { ...cached, cacheHit: true }); + } + + const heimdallVersion = await resolveHeimdallVersion(); + + let result; + try { + result = await withHeimdallSlot(() => runHeimdallSubprocess({ + bin: HEIMDALL_BIN_PATH, + args: ["dump", address, "--rpc-url", resolved.url, "--block", block.tag, "--output", "json"], + timeoutMs: HEIMDALL_DUMP_TIMEOUT_MS, + })); + } catch (err) { + const { status, code } = classifyRunError(err); + return sendError(res, status, code, err?.message, err?.stderr); + } + + if (result.exitCode !== 0) { + return sendError(res, 502, "heimdall_upstream_error", `heimdall exit ${result.exitCode}`, result.stderr); + } + + let raw; + try { + raw = JSON.parse(result.stdout); + } catch { + return sendError(res, 502, "heimdall_invalid_output", "heimdall did not return JSON", result.stdout.slice(0, 500)); + } + + const slots = normalizeDumpSlots(raw); + if (slots === null) { + return sendError(res, 502, "heimdall_invalid_output", "unexpected dump shape"); + } + + const response = { + address: normalizedAddress, + chainId, + blockNumber: typeof blockInput === "number" ? blockInput : 0, + slots, + heimdallVersion, + cacheHit: false, + generatedAt: Date.now(), + }; + if (cacheKey) resultCache.set(cacheKey, response); + sendOk(res, response); +} + +export async function handleHeimdall(url, body, res) { + if (url === "/heimdall/version") { + await handleVersion(res); + return true; + } + if (url === "/heimdall/decompile") { + await handleDecompile(body, res); + return true; + } + if (url === "/heimdall/dump") { + await handleDump(body, res); + return true; + } + return false; +} diff --git a/scripts/heimdall-runner.mjs b/scripts/heimdall-runner.mjs new file mode 100644 index 0000000..8c9f969 --- /dev/null +++ b/scripts/heimdall-runner.mjs @@ -0,0 +1,71 @@ +// scripts/heimdall-runner.mjs +// +// Spawn a heimdall (or arbitrary) subprocess with a hard timeout and return +// captured stdout/stderr. Throws classified errors so callers can map them to +// HTTP responses. + +import { spawn } from "node:child_process"; + +export class HeimdallRunError extends Error { + /** + * @param {string} code + * @param {string} message + * @param {{ stderr?: string, exitCode?: number }} [extra] + */ + constructor(code, message, { stderr, exitCode } = {}) { + super(message); + this.name = "HeimdallRunError"; + this.code = code; + this.stderr = stderr; + this.exitCode = exitCode; + } +} + +export function runHeimdallSubprocess({ bin, args = [], timeoutMs, stdin }) { + return new Promise((resolve, reject) => { + let child; + try { + child = spawn(bin, args, { stdio: ["pipe", "pipe", "pipe"] }); + } catch (err) { + return reject(new HeimdallRunError("HEIMDALL_NOT_INSTALLED", err.message)); + } + + const stdoutChunks = []; + const stderrChunks = []; + let settled = false; + + const timer = setTimeout(() => { + if (settled) return; + settled = true; + try { child.kill("SIGKILL"); } catch { /* already dead */ } + reject(new HeimdallRunError("HEIMDALL_TIMEOUT", `heimdall exceeded ${timeoutMs}ms`)); + }, timeoutMs); + + child.stdout.on("data", (c) => stdoutChunks.push(c)); + child.stderr.on("data", (c) => stderrChunks.push(c)); + + child.on("error", (err) => { + if (settled) return; + settled = true; + clearTimeout(timer); + const code = err.code === "ENOENT" ? "HEIMDALL_NOT_INSTALLED" : "HEIMDALL_SPAWN_FAILED"; + reject(new HeimdallRunError(code, err.message)); + }); + + child.on("close", (exitCode) => { + if (settled) return; + settled = true; + clearTimeout(timer); + resolve({ + exitCode: exitCode ?? -1, + stdout: Buffer.concat(stdoutChunks).toString("utf8"), + stderr: Buffer.concat(stderrChunks).toString("utf8"), + }); + }); + + if (stdin !== undefined) { + child.stdin.write(stdin); + } + child.stdin.end(); + }); +} diff --git a/scripts/simulator-bridge.mjs b/scripts/simulator-bridge.mjs index 0188ba0..9e2e8ec 100644 --- a/scripts/simulator-bridge.mjs +++ b/scripts/simulator-bridge.mjs @@ -68,6 +68,8 @@ import { runAsyncDebugPrep, } from "./debug-sessions.mjs"; +import { handleHeimdall } from "./heimdall-manager.mjs"; + // ============================================================================= // Startup Validation // ============================================================================= @@ -684,6 +686,10 @@ const server = http.createServer(async (req, res) => { } default: + if (url?.startsWith('/heimdall/')) { + const handled = await handleHeimdall(url, body, res); + if (handled) break; + } res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "not_found" })); } @@ -723,6 +729,9 @@ server.listen(PORT, () => { console.log(` POST /debug/end - End debug session`); console.log(` POST /debug/sessions - List active sessions`); console.log(` GET /health - Health check`); + console.log(` POST /heimdall/version - Check Heimdall CLI availability`); + console.log(` POST /heimdall/decompile - Decompile bytecode via Heimdall`); + console.log(` POST /heimdall/dump - Dump contract storage via Heimdall`); console.log( `[simulator-bridge] concurrency: max=${MAX_CONCURRENT_SIMULATIONS} processes, queue=${SIMULATION_QUEUE_MAX}, queue_timeout=${SIMULATION_QUEUE_TIMEOUT_MS}ms`, ); diff --git a/scripts/smoke-heimdall.mjs b/scripts/smoke-heimdall.mjs new file mode 100644 index 0000000..8e3a6ae --- /dev/null +++ b/scripts/smoke-heimdall.mjs @@ -0,0 +1,46 @@ +// scripts/smoke-heimdall.mjs +// +// Manual smoke test. Requires: +// 1. Bridge running: `npm run simulator:server` +// 2. heimdall binary on PATH (`heimdall --version` works) +// 3. The bridge has a server-side RPC allowlist entry for chain 1 +// (default mainnet URL is cloudflare-eth.com; override with +// HEIMDALL_RPC_BY_CHAIN env). +// +// The client never sends rpcUrl — chainId only. The bridge resolves RPC +// server-side and computes the bytecode hash itself from eth_getCode. +// +// Usage: +// EDB_API_KEY=... node scripts/smoke-heimdall.mjs + +const BRIDGE = process.env.BRIDGE_URL || "http://127.0.0.1:5789"; +const API_KEY = process.env.EDB_API_KEY || ""; + +const headers = { "Content-Type": "application/json" }; +if (API_KEY) headers["X-API-Key"] = API_KEY; + +async function call(path, body) { + const res = await fetch(`${BRIDGE}${path}`, { method: "POST", headers, body: JSON.stringify(body) }); + const text = await res.text(); + return { status: res.status, body: text }; +} + +console.log("1) /heimdall/version"); +console.log(await call("/heimdall/version", {})); + +const USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; +console.log("\n2) /heimdall/decompile (USDT via address+chainId)"); +const dec = await call("/heimdall/decompile", { + address: USDT, + chainId: 1, +}); +const decBody = dec.body ? JSON.parse(dec.body) : {}; +console.log({ status: dec.status, sourcePreview: decBody.source?.slice(0, 120) }); + +console.log("\n3) /heimdall/dump (USDT at block latest)"); +const dump = await call("/heimdall/dump", { + address: USDT, + chainId: 1, +}); +const dumpBody = dump.body ? JSON.parse(dump.body) : {}; +console.log({ status: dump.status, slotCount: dumpBody.slots?.length }); diff --git a/src/App.css b/src/App.css index 6c0d2b4..bae9196 100644 --- a/src/App.css +++ b/src/App.css @@ -166,18 +166,18 @@ body { opacity: 0.7; } -.rpc-settings-trigger { +.app-settings-trigger { color: rgba(199, 210, 254, 0.9); } -.rpc-settings-trigger::after { +.app-settings-trigger::after { border: 1px solid rgba(229, 229, 229, 0.45); box-shadow: 0 0 18px rgba(79, 70, 229, 0.4); opacity: 0.35; } -.rpc-settings-trigger:hover::after, -.rpc-settings-trigger:focus-visible::after { +.app-settings-trigger:hover::after, +.app-settings-trigger:focus-visible::after { opacity: 0.6; } diff --git a/src/App.tsx b/src/App.tsx index 22732d3..9819cd8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,7 @@ import { DebugProvider } from "./contexts/DebugContext"; import Navigation from "./components/Navigation"; import ErrorBoundary from "./components/ErrorBoundary"; import { NotificationProvider } from "./components/NotificationManager"; +import { LlmConsentGateProvider } from "./contexts/LlmConsentGateContext"; import { RouteMetaTags } from "./components/shared/RouteMetaTags"; import { useNetworkConfig } from "./contexts/NetworkConfigContext"; import { Button } from "./components/ui/button"; @@ -22,11 +23,14 @@ import MobileDrawer from "./components/MobileDrawer"; import { useBreakpoint } from "./hooks/useBreakpoint"; const SimulationResultsPage = React.lazy(() => import("./components/SimulationResultsPage")); -const RpcSettingsModal = React.lazy(() => import("./components/RpcSettingsModal")); +const SettingsModal = React.lazy(() => import("./components/SettingsModal")); const StorageManagerModal = React.lazy(() => import("./components/StorageManagerModal")); +const TxSummarySamplesPage = React.lazy( + () => import("./components/tx-analysis/TxSummarySamplesPage"), +); function App() { - const [isRpcModalOpen, setIsRpcModalOpen] = useState(false); + const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); const [isStorageModalOpen, setIsStorageModalOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const location = useLocation(); @@ -35,6 +39,7 @@ function App() { const isSimulationPage = location.pathname.startsWith("/simulation/"); const isHomePage = location.pathname === "/"; + const isTxSummarySamples = location.pathname === "/tx-summary-samples"; useApplyRainbowKitTheme(); @@ -49,13 +54,14 @@ function App() { + {/* Global top bar — visible on every route */} setIsRpcModalOpen(true)} + onOpenSettings={() => setIsSettingsModalOpen(true)} onOpenStorageManager={() => setIsStorageModalOpen(true)} onToggleMobileMenu={() => setIsMobileMenuOpen((v) => !v)} isMobileMenuOpen={isMobileMenuOpen} @@ -78,6 +84,12 @@ function App() { } /> + ) : isTxSummarySamples ? ( + }> + + } /> + + ) : (
{!isMobile && } @@ -141,7 +153,7 @@ function App() { type="button" variant="ghost" className="btn btn-primary" - onClick={() => setIsRpcModalOpen(true)} + onClick={() => setIsSettingsModalOpen(true)} style={{ padding: "10px 16px", borderRadius: "10px", @@ -183,11 +195,11 @@ function App() { - {isRpcModalOpen && ( + {isSettingsModalOpen && ( - setIsRpcModalOpen(false)} + setIsSettingsModalOpen(false)} /> )} @@ -202,6 +214,7 @@ function App() { )} + diff --git a/src/abi/HackTriage.json b/src/abi/HackTriage.json new file mode 100644 index 0000000..1031dd3 --- /dev/null +++ b/src/abi/HackTriage.json @@ -0,0 +1,145 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "HackTriage", + "sourceName": "contracts/HackTriage.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint8", + "name": "got", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "expected", + "type": "uint8" + } + ], + "name": "InvalidEncryptedInput", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int32", + "name": "value", + "type": "int32" + } + ], + "name": "SecurityZoneOutOfBounds", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "blockNumber", + "type": "uint64" + } + ], + "name": "TriageSubmitted", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "consumer", + "type": "address" + } + ], + "name": "allowConsumer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "latest", + "outputs": [ + { + "internalType": "euint16", + "name": "classBits", + "type": "bytes32" + }, + { + "internalType": "euint8", + "name": "severity", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "blockNumber", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "ctHash", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "securityZone", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "utype", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct InEuint16", + "name": "encFeatures", + "type": "tuple" + } + ], + "name": "triage", + "outputs": [ + { + "internalType": "euint16", + "name": "classBits", + "type": "bytes32" + }, + { + "internalType": "euint8", + "name": "severity", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608080604052346015576113e6908161001b8239f35b600080fdfe6080604081815260048036101561001557600080fd5b600092833560e01c9081632d94964f146100e6575080634a4aac1a1461008e5763538f8f5e1461004457600080fd5b3461008a57602036600319011261008a57356001600160a01b038116810361008a57600161008792338552846020528420610080838254610d99565b0154610d99565b80f35b8280fd5b503461008a57602036600319011261008a57356001600160a01b0381169081900361008a5782829160609452806020522080549167ffffffffffffffff60026001840154930154169181519384526020840152820152f35b849291503461008a57602090600319938285360112610a6a5783359467ffffffffffffffff9081871161008a576080908736030112610a665761012883610a6d565b85850135835260249261013c848801610b0d565b9685820197885261014f60448201610b0d565b90898301918252606481013590848211610a625701973660238a0112156109f9578789013598848a11610a5057601f19998b51916101948a8d601f8501160184610aeb565b81835236898383010111610a4c578188928a8c9301838601378301015260ff606093848601928352511693600394858103610a305750889160ff8d9481808e9589808e8b51906101e382610a6d565b808252808c8301528c82015201525192511693519387519261020484610a6d565b8352868301908152878301928a845289810195865288519889976313fce3b160e11b89528801525160448701525116606485015251166084830152519a608060a4830152878c51809d8160c4860152825b828110610a0e57505091839160e4809484010152338b830152601f73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d99e01168101030181888d5af1908115610a045785916109d3575b506102c26102b46102ae61112b565b83610deb565b6102bc61112b565b90610e90565b928a51936102cf85610acf565b8685528b516102dd81610a6d565b82815284368b83013760016102f182611070565b52826102fc82611093565b528051956002968710156109c157918a828f8f90958f97968d90818c61033899015251809781958294631888debd60e01b9c8d85528401610f9f565b03925af180156109b7578990610982575b610370925061035661127a565b9181811561088c575b1561087c575b821561086c57610fc4565b61038c61038461037e6111a1565b86610deb565b6102bc6111a1565b8d518e61039882610acf565b8a825251906103a682610a6d565b84825287368e840137886103b983611070565b52846103c483611093565b5281518910156108e2578f908f8f8f93898f916103f190838f8a0152519788968795869485528401610f9f565b03925af19081156108d7578a91610953575b5061041b92916104159161035661127a565b90610b20565b61043161042961037e6111d7565b6102bc6111d7565b8d518e61043d82610acf565b8a8252519061044b82610a6d565b84825287368e8401378d61045e83611070565b528461046983611093565b5281518910156108e2578f908f8f8f93898f9161049690838f8a0152519788968795869485528401610f9f565b03925af19081156108d7578a91610924575b506104ba92916104159161035661127a565b6104d06104c861037e61120d565b6102bc61120d565b8d518e6104dc82610acf565b8a825251906104ea82610a6d565b84825287368e84013760086104fe83611070565b528461050983611093565b5281518910156108e2578f908f8f8f93898f9161053690838f8a0152519788968795869485528401610f9f565b03925af19081156108d7578a916108f5575b5061055a92916104159161035661127a565b61057061056861037e611243565b6102bc611243565b8d518e61057c82610acf565b8a8252519061058a82610a6d565b84825287368e840137601061059e83611070565b52846105a983611093565b5281518910156108e2578f908f8f8f93898f916105d690838f8a0152519788968795869485528401610f9f565b03925af19081156108d757908f9392918b9161089c575b506105ff92916104159161035661127a565b9b6106ac6105686106a361068c6106756106638a61065d6106336102b46106246110a3565b9361062d61112b565b90610deb565b61063b611339565b6106436110a3565b9181811561080d575b156107fd575b82156107ed5761101a565b90610c40565b61065d6106336103848d61062d6111a1565b61065d6106336104296106866111d7565b8d610deb565b61065d6106336104c861069d61120d565b8c610deb565b9661062d611243565b938251926106b984610acf565b8a845251936106c785610a6d565b845286368d86013760016106da85611070565b52876106e585611093565b52835188101561085a57908c8f8d94936107108d92838c8a0152519788968795869485528401610f69565b03925af190811561085057879161081d575b50610734929161065d916106436110a3565b96834316958a5192830191838310868411176107dc5750508952878152858101878152898083019587875233815280895220915182555160018201550191511667ffffffffffffffff1982541617905561078d84610ce5565b61079683610ce5565b61079f84610d51565b6107a883610d51565b84519081527f64db072300a25101529d68cedafafdb06ffeea05e13ae2c825d4e63071135292823392a28351928352820152f35b634e487b7160e01b87526041905285fd5b91506107f76110a3565b9161101a565b90506108076110a3565b90610652565b90506108176112af565b9061064c565b90508881813d8311610849575b6108348183610aeb565b810103126108455751610734610722565b8680fd5b503d61082a565b8c513d89823e3d90fd5b634e487b7160e01b8a5260328d528a8afd5b915061087661127a565b91610fc4565b905061088661127a565b90610365565b90506108966112af565b9061035f565b80929394508d8092503d83116108d0575b6108b78183610aeb565b810103126108cc57518e9291906105ff6105ed565b8980fd5b503d6108ad565b508e513d8b823e3d90fd5b50634e487b7160e01b8a5260328d528a8afd5b90508b81813d831161091d575b61090c8183610aeb565b810103126108cc575161055a610548565b503d610902565b90508b81813d831161094c575b61093b8183610aeb565b810103126108cc57516104ba6104a8565b503d610931565b90508b81813d831161097b575b61096a8183610aeb565b810103126108cc575161041b610403565b503d610960565b50908a81813d83116109b0575b6109998183610aeb565b810103126109ac57906103709151610349565b8880fd5b503d61098f565b8e513d8b823e3d90fd5b634e487b7160e01b895260328c528989fd5b90508681813d83116109fd575b6109ea8183610aeb565b810103126109f957518a61029f565b8480fd5b503d6109e0565b8a513d87823e3d90fd5b925093505083818495939501015160e482870101520191878c85938c95610255565b8a868a8f9360449451936367cf307160e01b8552840152820152fd5b8780fd5b634e487b7160e01b8652604189528686fd5b8580fd5b5080fd5b80fd5b6080810190811067ffffffffffffffff821117610a8957604052565b634e487b7160e01b600052604160045260246000fd5b67ffffffffffffffff8111610a8957604052565b6060810190811067ffffffffffffffff821117610a8957604052565b6020810190811067ffffffffffffffff821117610a8957604052565b90601f8019910116810190811067ffffffffffffffff821117610a8957604052565b359060ff82168203610b1b57565b600080fd5b908115610c30575b8015610c22575b60405191610b3c83610ab3565b600283526040366020850137610b5183611070565b52610b5b82611093565b52604051610b6881610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b855260036004860152600b6024860152608060448601526084850190610f35565b83810360031901606485015290610f35565b03818573ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d95af1918215610c16578092610be557505090565b9091506020823d602011610c0e575b81610c0160209383610aeb565b81010312610a6a57505190565b3d9150610bf4565b604051903d90823e3d90fd5b50610c2b61127a565b610b2f565b9050610c3a61127a565b90610b28565b908115610cd5575b8015610cc7575b60405191610c5c83610ab3565b600283526040366020850137610c7183611070565b52610c7b82611093565b52604051610c8881610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526002600486015260086024860152608060448601526084850190610f35565b50610cd06110a3565b610c4f565b9050610cdf6110a3565b90610c48565b73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d9803b15610b1b57604051631974142760e21b815260048101929092523060248301526000908290818381604481015b03925af18015610d4557610d3a5750565b610d4390610a9f565b565b6040513d6000823e3d90fd5b73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d9803b15610b1b57604051631974142760e21b81526004810192909252336024830152600090829081838160448101610d29565b73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d991823b15610b1b57604051631974142760e21b815260048101929092526001600160a01b0316602482015290600090829081838160448101610d29565b908115610e80575b8015610e72575b60405191610e0783610ab3565b600283526040366020850137610e1c83611070565b52610e2682611093565b52604051610e3381610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b855260036004860152600a6024860152608060448601526084850190610f35565b50610e7b61127a565b610dfa565b9050610e8a61127a565b90610df3565b908115610f25575b8015610f17575b60405191610eac83610ab3565b600283526040366020850137610ec183611070565b52610ecb82611093565b52604051610ed881610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526003600486015260186024860152608060448601526084850190610f35565b50610f2061127a565b610e9f565b9050610f2f61127a565b90610e98565b90815180825260208080930193019160005b828110610f55575050505090565b835185529381019392810192600101610f47565b9091610f8e610f9c9360028452601a6020850152608060408501526080840190610f35565b916060818403910152610f35565b90565b9091610f8e610f9c9360038452601a6020850152608060408501526080840190610f35565b90610fcf929161136f565b604051610fdb81610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526003600486015260046024860152608060448601526084850190610f35565b90611025929161136f565b60405161103181610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526002600486015260046024860152608060448601526084850190610f35565b80511561107d5760200190565b634e487b7160e01b600052603260045260246000fd5b80516001101561107d5760400190565b6040516110af81610acf565b600090818152604051906110c282610a6d565b600382526060366020840137826110d883611070565b5260026110e483611093565b528151600210156111175781602091846060610bb99501526040519384928392631888debd60e01b845260048401610f69565b634e487b7160e01b83526032600452602483fd5b60405161113781610acf565b6000908181526040519061114a82610a6d565b60038252606036602084013761018161116283611070565b52600361116e83611093565b528151600210156111175781602091846060610bb99501526040519384928392631888debd60e01b845260048401610f9f565b6040516111ad81610acf565b600090818152604051906111c082610a6d565b600382526060366020840137601c61116283611070565b6040516111e381610acf565b600090818152604051906111f682610a6d565b600382526060366020840137604261116283611070565b60405161121981610acf565b6000908181526040519061122c82610a6d565b600382526060366020840137602c61116283611070565b60405161124f81610acf565b6000908181526040519061126282610a6d565b60038252606036602084013761024061116283611070565b60405161128681610acf565b6000908181526040519061129982610a6d565b6003825260603660208401378261116283611070565b6040516112bb81610acf565b600090818152604051906112ce82610a6d565b600382526060366020840137826112e483611070565b52826112ef83611093565b5281516002101561111757610ba7602091846060850152610bb96040519485938493631888debd60e01b8552886004860152601a6024860152608060448601526084850190610f35565b60405161134581610acf565b6000908181526040519061135882610a6d565b60038252606036602084013760026110d883611070565b91906040519261137e84610a6d565b60038452606036602086013761139384611070565b5261139d83611093565b5281516002101561107d5760608201529056fea2646970667358221220ce7f8bd5a192235b09db408f851e25710f1639f5d8d73cd360853811de0ee52264736f6c63430008190033", + "deployedBytecode": "0x6080604081815260048036101561001557600080fd5b600092833560e01c9081632d94964f146100e6575080634a4aac1a1461008e5763538f8f5e1461004457600080fd5b3461008a57602036600319011261008a57356001600160a01b038116810361008a57600161008792338552846020528420610080838254610d99565b0154610d99565b80f35b8280fd5b503461008a57602036600319011261008a57356001600160a01b0381169081900361008a5782829160609452806020522080549167ffffffffffffffff60026001840154930154169181519384526020840152820152f35b849291503461008a57602090600319938285360112610a6a5783359467ffffffffffffffff9081871161008a576080908736030112610a665761012883610a6d565b85850135835260249261013c848801610b0d565b9685820197885261014f60448201610b0d565b90898301918252606481013590848211610a625701973660238a0112156109f9578789013598848a11610a5057601f19998b51916101948a8d601f8501160184610aeb565b81835236898383010111610a4c578188928a8c9301838601378301015260ff606093848601928352511693600394858103610a305750889160ff8d9481808e9589808e8b51906101e382610a6d565b808252808c8301528c82015201525192511693519387519261020484610a6d565b8352868301908152878301928a845289810195865288519889976313fce3b160e11b89528801525160448701525116606485015251166084830152519a608060a4830152878c51809d8160c4860152825b828110610a0e57505091839160e4809484010152338b830152601f73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d99e01168101030181888d5af1908115610a045785916109d3575b506102c26102b46102ae61112b565b83610deb565b6102bc61112b565b90610e90565b928a51936102cf85610acf565b8685528b516102dd81610a6d565b82815284368b83013760016102f182611070565b52826102fc82611093565b528051956002968710156109c157918a828f8f90958f97968d90818c61033899015251809781958294631888debd60e01b9c8d85528401610f9f565b03925af180156109b7578990610982575b610370925061035661127a565b9181811561088c575b1561087c575b821561086c57610fc4565b61038c61038461037e6111a1565b86610deb565b6102bc6111a1565b8d518e61039882610acf565b8a825251906103a682610a6d565b84825287368e840137886103b983611070565b52846103c483611093565b5281518910156108e2578f908f8f8f93898f916103f190838f8a0152519788968795869485528401610f9f565b03925af19081156108d7578a91610953575b5061041b92916104159161035661127a565b90610b20565b61043161042961037e6111d7565b6102bc6111d7565b8d518e61043d82610acf565b8a8252519061044b82610a6d565b84825287368e8401378d61045e83611070565b528461046983611093565b5281518910156108e2578f908f8f8f93898f9161049690838f8a0152519788968795869485528401610f9f565b03925af19081156108d7578a91610924575b506104ba92916104159161035661127a565b6104d06104c861037e61120d565b6102bc61120d565b8d518e6104dc82610acf565b8a825251906104ea82610a6d565b84825287368e84013760086104fe83611070565b528461050983611093565b5281518910156108e2578f908f8f8f93898f9161053690838f8a0152519788968795869485528401610f9f565b03925af19081156108d7578a916108f5575b5061055a92916104159161035661127a565b61057061056861037e611243565b6102bc611243565b8d518e61057c82610acf565b8a8252519061058a82610a6d565b84825287368e840137601061059e83611070565b52846105a983611093565b5281518910156108e2578f908f8f8f93898f916105d690838f8a0152519788968795869485528401610f9f565b03925af19081156108d757908f9392918b9161089c575b506105ff92916104159161035661127a565b9b6106ac6105686106a361068c6106756106638a61065d6106336102b46106246110a3565b9361062d61112b565b90610deb565b61063b611339565b6106436110a3565b9181811561080d575b156107fd575b82156107ed5761101a565b90610c40565b61065d6106336103848d61062d6111a1565b61065d6106336104296106866111d7565b8d610deb565b61065d6106336104c861069d61120d565b8c610deb565b9661062d611243565b938251926106b984610acf565b8a845251936106c785610a6d565b845286368d86013760016106da85611070565b52876106e585611093565b52835188101561085a57908c8f8d94936107108d92838c8a0152519788968795869485528401610f69565b03925af190811561085057879161081d575b50610734929161065d916106436110a3565b96834316958a5192830191838310868411176107dc5750508952878152858101878152898083019587875233815280895220915182555160018201550191511667ffffffffffffffff1982541617905561078d84610ce5565b61079683610ce5565b61079f84610d51565b6107a883610d51565b84519081527f64db072300a25101529d68cedafafdb06ffeea05e13ae2c825d4e63071135292823392a28351928352820152f35b634e487b7160e01b87526041905285fd5b91506107f76110a3565b9161101a565b90506108076110a3565b90610652565b90506108176112af565b9061064c565b90508881813d8311610849575b6108348183610aeb565b810103126108455751610734610722565b8680fd5b503d61082a565b8c513d89823e3d90fd5b634e487b7160e01b8a5260328d528a8afd5b915061087661127a565b91610fc4565b905061088661127a565b90610365565b90506108966112af565b9061035f565b80929394508d8092503d83116108d0575b6108b78183610aeb565b810103126108cc57518e9291906105ff6105ed565b8980fd5b503d6108ad565b508e513d8b823e3d90fd5b50634e487b7160e01b8a5260328d528a8afd5b90508b81813d831161091d575b61090c8183610aeb565b810103126108cc575161055a610548565b503d610902565b90508b81813d831161094c575b61093b8183610aeb565b810103126108cc57516104ba6104a8565b503d610931565b90508b81813d831161097b575b61096a8183610aeb565b810103126108cc575161041b610403565b503d610960565b50908a81813d83116109b0575b6109998183610aeb565b810103126109ac57906103709151610349565b8880fd5b503d61098f565b8e513d8b823e3d90fd5b634e487b7160e01b895260328c528989fd5b90508681813d83116109fd575b6109ea8183610aeb565b810103126109f957518a61029f565b8480fd5b503d6109e0565b8a513d87823e3d90fd5b925093505083818495939501015160e482870101520191878c85938c95610255565b8a868a8f9360449451936367cf307160e01b8552840152820152fd5b8780fd5b634e487b7160e01b8652604189528686fd5b8580fd5b5080fd5b80fd5b6080810190811067ffffffffffffffff821117610a8957604052565b634e487b7160e01b600052604160045260246000fd5b67ffffffffffffffff8111610a8957604052565b6060810190811067ffffffffffffffff821117610a8957604052565b6020810190811067ffffffffffffffff821117610a8957604052565b90601f8019910116810190811067ffffffffffffffff821117610a8957604052565b359060ff82168203610b1b57565b600080fd5b908115610c30575b8015610c22575b60405191610b3c83610ab3565b600283526040366020850137610b5183611070565b52610b5b82611093565b52604051610b6881610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b855260036004860152600b6024860152608060448601526084850190610f35565b83810360031901606485015290610f35565b03818573ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d95af1918215610c16578092610be557505090565b9091506020823d602011610c0e575b81610c0160209383610aeb565b81010312610a6a57505190565b3d9150610bf4565b604051903d90823e3d90fd5b50610c2b61127a565b610b2f565b9050610c3a61127a565b90610b28565b908115610cd5575b8015610cc7575b60405191610c5c83610ab3565b600283526040366020850137610c7183611070565b52610c7b82611093565b52604051610c8881610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526002600486015260086024860152608060448601526084850190610f35565b50610cd06110a3565b610c4f565b9050610cdf6110a3565b90610c48565b73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d9803b15610b1b57604051631974142760e21b815260048101929092523060248301526000908290818381604481015b03925af18015610d4557610d3a5750565b610d4390610a9f565b565b6040513d6000823e3d90fd5b73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d9803b15610b1b57604051631974142760e21b81526004810192909252336024830152600090829081838160448101610d29565b73ea30c4b8b44078bbf8a6ef5b9f1ec1626c7848d991823b15610b1b57604051631974142760e21b815260048101929092526001600160a01b0316602482015290600090829081838160448101610d29565b908115610e80575b8015610e72575b60405191610e0783610ab3565b600283526040366020850137610e1c83611070565b52610e2682611093565b52604051610e3381610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b855260036004860152600a6024860152608060448601526084850190610f35565b50610e7b61127a565b610dfa565b9050610e8a61127a565b90610df3565b908115610f25575b8015610f17575b60405191610eac83610ab3565b600283526040366020850137610ec183611070565b52610ecb82611093565b52604051610ed881610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526003600486015260186024860152608060448601526084850190610f35565b50610f2061127a565b610e9f565b9050610f2f61127a565b90610e98565b90815180825260208080930193019160005b828110610f55575050505090565b835185529381019392810192600101610f47565b9091610f8e610f9c9360028452601a6020850152608060408501526080840190610f35565b916060818403910152610f35565b90565b9091610f8e610f9c9360038452601a6020850152608060408501526080840190610f35565b90610fcf929161136f565b604051610fdb81610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526003600486015260046024860152608060448601526084850190610f35565b90611025929161136f565b60405161103181610acf565b6020610ba7600093848452610bb96040519485938493631888debd60e01b85526002600486015260046024860152608060448601526084850190610f35565b80511561107d5760200190565b634e487b7160e01b600052603260045260246000fd5b80516001101561107d5760400190565b6040516110af81610acf565b600090818152604051906110c282610a6d565b600382526060366020840137826110d883611070565b5260026110e483611093565b528151600210156111175781602091846060610bb99501526040519384928392631888debd60e01b845260048401610f69565b634e487b7160e01b83526032600452602483fd5b60405161113781610acf565b6000908181526040519061114a82610a6d565b60038252606036602084013761018161116283611070565b52600361116e83611093565b528151600210156111175781602091846060610bb99501526040519384928392631888debd60e01b845260048401610f9f565b6040516111ad81610acf565b600090818152604051906111c082610a6d565b600382526060366020840137601c61116283611070565b6040516111e381610acf565b600090818152604051906111f682610a6d565b600382526060366020840137604261116283611070565b60405161121981610acf565b6000908181526040519061122c82610a6d565b600382526060366020840137602c61116283611070565b60405161124f81610acf565b6000908181526040519061126282610a6d565b60038252606036602084013761024061116283611070565b60405161128681610acf565b6000908181526040519061129982610a6d565b6003825260603660208401378261116283611070565b6040516112bb81610acf565b600090818152604051906112ce82610a6d565b600382526060366020840137826112e483611070565b52826112ef83611093565b5281516002101561111757610ba7602091846060850152610bb96040519485938493631888debd60e01b8552886004860152601a6024860152608060448601526084850190610f35565b60405161134581610acf565b6000908181526040519061135882610a6d565b60038252606036602084013760026110d883611070565b91906040519261137e84610a6d565b60038452606036602086013761139384611070565b5261139d83611093565b5281516002101561107d5760608201529056fea2646970667358221220ce7f8bd5a192235b09db408f851e25710f1639f5d8d73cd360853811de0ee52264736f6c63430008190033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/src/chains/index.ts b/src/chains/index.ts deleted file mode 100644 index 3f379c9..0000000 --- a/src/chains/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { - CHAIN_REGISTRY, - SUPPORTED_CHAINS, - getChainById, - getExplorerChains, - getMainnetChains, - getTestnetChains, - isTestnet, - getExplorerBaseUrlFromApiUrl, - getExplorerUrl, - PUBLIC_RPC_MAP, -} from "./registry"; diff --git a/src/components/ExecutionStackTrace.tsx b/src/components/ExecutionStackTrace.tsx index e64a9b5..f5aec7f 100644 --- a/src/components/ExecutionStackTrace.tsx +++ b/src/components/ExecutionStackTrace.tsx @@ -12,6 +12,7 @@ import { AccordionTrigger, } from "./ui/accordion"; import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "./ui/table"; +import { extractTokenMovements } from "../utils/tokenMovements"; // Re-export types for backward compatibility export type { TraceRow, TraceFilters, DecodedLogData } from "./execution-trace"; @@ -134,8 +135,31 @@ const ExecutionStackTrace: React.FC = (props) => { }, [assetChanges]); const hasAssetChanges = orderedAssetChanges.rows.length > 0; - const hasTokenEvents = traceEvents && traceEvents.length > 0; - const defaultAccordionItems: string[] = []; + + // Pre-compute actual token transfer count so we only show the accordion + // when there are real Transfer events (not just any LOG opcode). + const tokenMovements = React.useMemo( + () => (traceEvents && traceEvents.length > 0 ? extractTokenMovements(traceEvents) : []), + [traceEvents] + ); + const hasTokenMovements = tokenMovements.length > 0; + + // Auto-expand accordion items when data becomes available. + // We use controlled state because `defaultValue` only applies at mount — + // if the component mounts while trace is still decoding, the accordion + // would stay collapsed even after data arrives. + const [openAccordionItems, setOpenAccordionItems] = React.useState([]); + const hasAutoExpandedRef = React.useRef(false); + React.useEffect(() => { + if (hasAutoExpandedRef.current) return; + const items: string[] = []; + if (hasAssetChanges) items.push("native-token-change"); + if (hasTokenMovements) items.push("token-movements"); + if (items.length > 0) { + setOpenAccordionItems(items); + hasAutoExpandedRef.current = true; + } + }, [hasAssetChanges, hasTokenMovements]); return (
@@ -184,8 +208,8 @@ const ExecutionStackTrace: React.FC = (props) => { {/* Asset Movements Accordion (Native Token Change + Token Movements) */} - {(hasAssetChanges || hasTokenEvents) && ( - + {(hasAssetChanges || hasTokenMovements) && ( + {/* Native Token Change */} {hasAssetChanges && ( @@ -244,16 +268,16 @@ const ExecutionStackTrace: React.FC = (props) => { )} {/* Token Movements */} - {hasTokenEvents && ( + {hasTokenMovements && ( Token Movements - {traceEvents!.length} + {tokenMovements.length} }, { id: "simulation", label: "Simulation (EDB)", shortLabel: "Sim", paramKey: "mode", icon: }, + { id: "analysis", label: "Analysis", shortLabel: "Analysis", paramKey: "mode", icon: }, ], }, { diff --git a/src/components/RpcSettingsModal.tsx b/src/components/RpcSettingsModal.tsx deleted file mode 100644 index 64995bc..0000000 --- a/src/components/RpcSettingsModal.tsx +++ /dev/null @@ -1,469 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import { - networkConfigManager, - type ExplorerKeyMode, - type RpcProviderMode, - isValidRpcUrl, -} from "../config/networkConfig"; -import type { Chain } from "../types"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "./ui/dialog"; -import { Button } from "./ui/button"; -import { Input } from "./ui/input"; -import { Label } from "./ui/label"; -import { Badge } from "./ui/badge"; -import { Tabs, TabsList, TabsTrigger } from "./ui/tabs"; -import { RadioGroup, RadioGroupItem } from "./ui/radio-group"; -import { Checkbox } from "./ui/checkbox"; -import { Eye, EyeSlash, CheckCircle, WarningCircle, CircleNotch } from "@phosphor-icons/react"; -import { cn } from "@/lib/utils"; - -interface RpcSettingsModalProps { - isOpen: boolean; - onClose: () => void; - onSaved?: () => void; - currentChain?: Chain | null; -} - -type FormState = { - mode: RpcProviderMode; - alchemyApiKey: string; - infuraProjectId: string; - customRpcUrl: string; - etherscanKeyMode: ExplorerKeyMode; - rememberPersonalEtherscanKey: boolean; - etherscanApiKey: string; -}; - -type ErrorKey = RpcProviderMode | "ETHERSCAN"; -type AutoSaveState = "idle" | "saving" | "saved" | "error"; - -const RpcSettingsModal: React.FC = ({ - isOpen, - onClose, - onSaved, -}) => { - const [formState, setFormState] = useState({ - mode: "DEFAULT", - alchemyApiKey: "", - infuraProjectId: "", - customRpcUrl: "", - etherscanKeyMode: "default", - rememberPersonalEtherscanKey: false, - etherscanApiKey: "", - }); - const [errors, setErrors] = useState>>({}); - const [autoSaveState, setAutoSaveState] = useState("idle"); - const [showAlchemyKey, setShowAlchemyKey] = useState(false); - const [showInfuraKey, setShowInfuraKey] = useState(false); - const [showEtherscanKey, setShowEtherscanKey] = useState(false); - const autoSaveTimer = useRef(null); - const initialSyncRef = useRef(true); - - useEffect(() => { - if (!isOpen) return; - const config = networkConfigManager.getConfig(); - setFormState({ - mode: config.rpcMode ?? "DEFAULT", - alchemyApiKey: config.alchemyApiKey ?? "", - infuraProjectId: config.infuraProjectId ?? "", - customRpcUrl: config.customRpcUrl ?? "", - etherscanKeyMode: config.etherscanKeyMode ?? "default", - rememberPersonalEtherscanKey: - config.rememberPersonalEtherscanKey ?? false, - etherscanApiKey: config.etherscanApiKey ?? "", - }); - setShowAlchemyKey(false); - setShowInfuraKey(false); - setShowEtherscanKey(false); - setErrors({}); - setAutoSaveState("saved"); - initialSyncRef.current = true; - }, [isOpen]); - - const formStateRef = useRef(formState); - formStateRef.current = formState; - useEffect(() => { - if (!isOpen) return; - return () => { - if (autoSaveTimer.current) { - window.clearTimeout(autoSaveTimer.current); - autoSaveTimer.current = null; - const fs = formStateRef.current; - networkConfigManager.saveConfig({ - rpcMode: fs.mode, - alchemyApiKey: fs.alchemyApiKey.trim(), - infuraProjectId: fs.infuraProjectId.trim(), - customRpcUrl: fs.customRpcUrl.trim(), - etherscanKeyMode: fs.etherscanKeyMode, - rememberPersonalEtherscanKey: fs.rememberPersonalEtherscanKey, - etherscanApiKey: fs.etherscanApiKey.trim(), - }); - } - }; - }, [isOpen]); - - const computeErrors = (state: FormState) => { - const nextErrors: typeof errors = {}; - if (state.mode === "ALCHEMY" && !state.alchemyApiKey.trim()) { - nextErrors.ALCHEMY = "API key required"; - } - if (state.mode === "INFURA" && !state.infuraProjectId.trim()) { - nextErrors.INFURA = "Project ID required"; - } - if (state.mode === "CUSTOM") { - if (!state.customRpcUrl.trim()) { - nextErrors.CUSTOM = "RPC URL required"; - } else if (!isValidRpcUrl(state.customRpcUrl)) { - nextErrors.CUSTOM = "Enter a valid HTTP(s) URL"; - } - } - if ( - state.etherscanKeyMode === "personal" && - !state.etherscanApiKey.trim() - ) { - nextErrors.ETHERSCAN = "Personal API key required"; - } - return nextErrors; - }; - - useEffect(() => { - if (!isOpen) return; - - const nextErrors = computeErrors(formState); - setErrors(nextErrors); - - if (initialSyncRef.current) { - initialSyncRef.current = false; - setAutoSaveState(Object.keys(nextErrors).length ? "error" : "saved"); - return; - } - - if (autoSaveTimer.current) { - window.clearTimeout(autoSaveTimer.current); - } - setAutoSaveState("saving"); - - autoSaveTimer.current = window.setTimeout(() => { - autoSaveTimer.current = null; - networkConfigManager.saveConfig({ - rpcMode: formState.mode, - alchemyApiKey: formState.alchemyApiKey.trim(), - infuraProjectId: formState.infuraProjectId.trim(), - customRpcUrl: formState.customRpcUrl.trim(), - etherscanKeyMode: formState.etherscanKeyMode, - rememberPersonalEtherscanKey: formState.rememberPersonalEtherscanKey, - etherscanApiKey: formState.etherscanApiKey.trim(), - }); - const hasErrors = Object.keys(nextErrors).length > 0; - setAutoSaveState(hasErrors ? "error" : "saved"); - if (onSaved) onSaved(); - }, 500); - - return () => { - if (autoSaveTimer.current) { - window.clearTimeout(autoSaveTimer.current); - } - }; - }, [formState, isOpen, onSaved]); - - return ( - !open && onClose()}> - - - RPC Provider Settings - - Configure your RPC provider. Personal settings stay in your browser; - the app default explorer key stays server-side. - - - -
- - setFormState((prev) => ({ - ...prev, - mode: value as RpcProviderMode, - })) - } - > - - - Default - - - Alchemy - - - Infura - - - Custom - - - - -
- -
- {formState.mode === "DEFAULT" ? ( - - ) : formState.mode === "ALCHEMY" ? ( - - setFormState((prev) => ({ - ...prev, - alchemyApiKey: e.target.value, - })) - } - placeholder="Enter API key..." - className={cn( - "flex-1", - errors.ALCHEMY && "border-destructive", - )} - /> - ) : formState.mode === "INFURA" ? ( - - setFormState((prev) => ({ - ...prev, - infuraProjectId: e.target.value, - })) - } - placeholder="Enter project ID..." - className={cn( - "flex-1", - errors.INFURA && "border-destructive", - )} - /> - ) : ( - - setFormState((prev) => ({ - ...prev, - customRpcUrl: e.target.value, - })) - } - placeholder="https://your-node.example.com" - className={cn( - "flex-1", - errors.CUSTOM && "border-destructive", - )} - /> - )} - {formState.mode !== "DEFAULT" && formState.mode !== "CUSTOM" && ( - - )} -
- {(errors.ALCHEMY || errors.INFURA || errors.CUSTOM) && ( -

- {errors.ALCHEMY || errors.INFURA || errors.CUSTOM} -

- )} -
- -
-
- - - Optional - -
- - setFormState((prev) => ({ - ...prev, - etherscanKeyMode: value as ExplorerKeyMode, - })) - } - className="gap-2" - > - - - - - {formState.etherscanKeyMode === "personal" ? ( -
-
- - setFormState((prev) => ({ - ...prev, - etherscanApiKey: e.target.value, - })) - } - placeholder="Enter API key..." - className={cn( - "flex-1", - errors.ETHERSCAN && "border-destructive", - )} - /> - -
- - {errors.ETHERSCAN && ( -

{errors.ETHERSCAN}

- )} -

- Personal keys stored in the browser are still visible to - browser scripts and extensions on this device. -

-
- ) : ( -

- Default mode keeps the shared explorer key off the client and - works across supported Etherscan-style networks. -

- )} -
-
- -
-
- {autoSaveState === "saving" && ( - <> - - Saving... - - )} - {autoSaveState === "saved" && ( - <> - - Saved - - )} - {autoSaveState === "error" && ( - <> - - Check fields - - )} -
- -
-
-
- ); -}; - -export default RpcSettingsModal; diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx new file mode 100644 index 0000000..57d0224 --- /dev/null +++ b/src/components/SettingsModal.tsx @@ -0,0 +1,61 @@ +import React, { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "./ui/dialog"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"; +import { GlobeHemisphereWest, Robot } from "@phosphor-icons/react"; +import RpcSettingsPanel from "./settings/RpcSettingsPanel"; +import LlmSettingsPanel from "./settings/LlmSettingsPanel"; + +interface SettingsModalProps { + isOpen: boolean; + onClose: () => void; + initialTab?: "network" | "llm"; +} + +const SettingsModal: React.FC = ({ + isOpen, + onClose, + initialTab = "network", +}) => { + const [tab, setTab] = useState<"network" | "llm">(initialTab); + + return ( + !open && onClose()}> + + + Settings + + Configure network providers and the LLM that powers analysis. + + + + setTab(v as "network" | "llm")}> + + + + Network + + + + LLM + + + + + + + + + + + + + ); +}; + +export default SettingsModal; diff --git a/src/components/SimulationResultsPage.tsx b/src/components/SimulationResultsPage.tsx index 0880d73..0ea3761 100644 --- a/src/components/SimulationResultsPage.tsx +++ b/src/components/SimulationResultsPage.tsx @@ -1,4 +1,4 @@ -import React, { Suspense } from "react"; +import React, { Suspense, useCallback } from "react"; import { ArrowLeft } from "@phosphor-icons/react"; import { Tabs, TabsList, TabsTrigger } from "./ui/tabs"; import { AnimatedTabContent } from "./ui/animated-tabs"; @@ -8,6 +8,8 @@ import "../styles/SimulationResultsPage.css"; // Sub-module imports import type { SimulationResultsPageProps, SimulatorTab } from "./simulation-results/types"; import { useSimulationPageState } from "./simulation-results/useSimulationPageState"; +import { useSimulation } from "../contexts/SimulationContext"; +import type { BridgeSimulationResponsePayload } from "../utils/transaction-simulation/types"; import { resolveFunctionName, computeGasValues, resolveReturnData } from "./simulation-results/gasHelpers"; import type { ContractContextExtras } from "./simulation-results/useSimulationPageState"; import { ResultsHeader } from "./simulation-results/ResultsHeader"; @@ -24,6 +26,7 @@ const DebugWindowWithContext = React.lazy(async () => { const SimulationResultsPage: React.FC = (props) => { const state = useSimulationPageState(props); + const { setAnalysisSubject } = useSimulation(); const { id, navigate, @@ -115,6 +118,20 @@ const SimulationResultsPage: React.FC = (props) => { const contextWithExtras = contractContext as (typeof contractContext & ContractContextExtras); + const handleSummarize = useCallback(() => { + const simulationId = id || contextSimulationId; + if (!simulationId || !result) return; + const txHash = contextWithExtras?.replayTxHash ?? null; + setAnalysisSubject({ + simulationId, + from, + to, + txHash, + simulation: result as unknown as BridgeSimulationResponsePayload, + }); + navigate("/builder?mode=analysis"); + }, [id, contextSimulationId, result, contextWithExtras?.replayTxHash, from, to, setAnalysisSubject, navigate]); + return (
{!isDebugging && ( @@ -135,6 +152,7 @@ const SimulationResultsPage: React.FC = (props) => { hasLiveDebugSession={hasLiveDebugSession} debugPrepState={debugPrepState} cancelDebugPrep={cancelDebugPrep} + handleSummarize={handleSummarize} /> = ({ events = [], @@ -256,7 +256,7 @@ const TokenMovementsPanel: React.FC = ({ // Handle empty state if (movements.length === 0) { - return null; // Don't render anything if no token movements + return null; } return ( diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index 8629794..0c416a8 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -17,7 +17,7 @@ import { cn } from "@/lib/utils"; import { useBreakpoint } from "@/hooks/useBreakpoint"; interface TopBarProps { - onOpenRpcSettings: () => void; + onOpenSettings: () => void; onOpenStorageManager: () => void; className?: string; onToggleMobileMenu?: () => void; @@ -25,7 +25,7 @@ interface TopBarProps { } const TopBar: React.FC = ({ - onOpenRpcSettings, + onOpenSettings, onOpenStorageManager, className, onToggleMobileMenu, @@ -170,7 +170,7 @@ const TopBar: React.FC = ({
@@ -217,14 +217,14 @@ const TopBar: React.FC = ({ type="button" variant="icon-borderless" size="icon-inline" - className="rpc-settings-trigger relative" + className="app-settings-trigger relative" onClick={(e) => { e.preventDefault(); setPopoverOpen(false); - onOpenRpcSettings(); + onOpenSettings(); }} - title="RPC Settings" - aria-label="RPC settings" + title="Settings" + aria-label="Settings" > {needsKeys && ( diff --git a/src/components/TransactionBuilderHub.tsx b/src/components/TransactionBuilderHub.tsx index 6aa6ca2..a330ac0 100644 --- a/src/components/TransactionBuilderHub.tsx +++ b/src/components/TransactionBuilderHub.tsx @@ -7,28 +7,46 @@ import { LayoutTransitionWrapper } from "./ui/animated-tabs"; import { useSimulation } from "../contexts/SimulationContext"; import { AnimatedZapIcon, AnimatedPlayIcon } from "./icons/IconLibrary"; import { SUPPORTED_CHAINS } from "../utils/chains"; +import type { BridgeSimulationResponsePayload } from "../utils/transaction-simulation/types"; -type BuilderMode = "live" | "simulation"; -type BuilderIntentMode = "live" | "simulation" | "replay"; +type BuilderMode = "live" | "simulation" | "analysis"; +type BuilderIntentMode = "live" | "simulation" | "replay" | "analysis"; const TXHASH_REPLAY_KEY = 'web3-toolkit:txhash-replay'; const loadSimpleGridUI = () => import("./SimpleGridUI"); const loadTransactionBuilderWagmi = () => import("./TransactionBuilderWagmi"); +const loadTxAnalysisPanel = () => import("./tx-analysis/TxAnalysisPanel"); const SimpleGridUI = React.lazy(loadSimpleGridUI); const TransactionBuilderWagmi = React.lazy(loadTransactionBuilderWagmi); +const TxAnalysisPanel = React.lazy(loadTxAnalysisPanel); function parseBuilderIntentMode(search: string): BuilderIntentMode | null { const mode = new URLSearchParams(search).get('mode'); - if (mode === 'live' || mode === 'simulation' || mode === 'replay') { + if (mode === 'live' || mode === 'simulation' || mode === 'replay' || mode === 'analysis') { return mode; } return null; } const TransactionBuilderHub: React.FC = () => { - const { contractContext } = useSimulation(); + const { contractContext, analysisSubject, currentSimulation, simulationId } = useSimulation(); + + const resolvedAnalysisSubject = useMemo(() => { + if (analysisSubject) return analysisSubject; + if (!currentSimulation || !simulationId) return null; + const from = currentSimulation.from ?? null; + const to = currentSimulation.to ?? contractContext?.address ?? null; + if (!from || !to) return null; + return { + simulationId, + from, + to, + txHash: contractContext?.replayTxHash ?? null, + simulation: currentSimulation as unknown as BridgeSimulationResponsePayload, + }; + }, [analysisSubject, currentSimulation, simulationId, contractContext?.address, contractContext?.replayTxHash]); const location = useLocation(); const urlIntentMode = parseBuilderIntentMode(location.search); const builderSearchParams = useMemo(() => new URLSearchParams(location.search), [location.search]); @@ -81,7 +99,12 @@ const TransactionBuilderHub: React.FC = () => { // Explicit URL mode intent should win when present. useEffect(() => { if (!urlIntentMode) return; - const nextMode: BuilderMode = urlIntentMode === 'live' ? 'live' : 'simulation'; + const nextMode: BuilderMode = + urlIntentMode === 'live' + ? 'live' + : urlIntentMode === 'analysis' + ? 'analysis' + : 'simulation'; setMode((prev) => (prev === nextMode ? prev : nextMode)); }, [urlIntentMode]); @@ -129,7 +152,19 @@ const TransactionBuilderHub: React.FC = () => {
}> - {mode === "live" ? : } + {mode === "live" ? ( + + ) : mode === "analysis" ? ( + + ) : ( + + )}
diff --git a/src/components/UniversalSearchBar.tsx b/src/components/UniversalSearchBar.tsx index a7d15c3..9d0db29 100644 --- a/src/components/UniversalSearchBar.tsx +++ b/src/components/UniversalSearchBar.tsx @@ -81,13 +81,13 @@ function getInputBadge(type: InputType): TypeBadgeConfig | null { interface UniversalSearchBarProps { className?: string; - onOpenRpcSettings?: () => void; + onOpenSettings?: () => void; onOpenStorageManager?: () => void; } const UniversalSearchBar: React.FC = ({ className, - onOpenRpcSettings, + onOpenSettings, onOpenStorageManager, }) => { const [open, setOpen] = useState(false); @@ -292,24 +292,28 @@ const UniversalSearchBar: React.FC = ({ Connect Wallet - {onOpenRpcSettings && ( + {onOpenSettings && ( { handleOpenChange(false); - onOpenRpcSettings(); + onOpenSettings(); }} > - RPC Settings + Settings )} {onOpenStorageManager && ( diff --git a/src/components/contract/ContractAddressInput.tsx b/src/components/contract/ContractAddressInput.tsx index 707f571..e5df2c1 100644 --- a/src/components/contract/ContractAddressInput.tsx +++ b/src/components/contract/ContractAddressInput.tsx @@ -72,7 +72,6 @@ export interface ContractAddressInputProps { | "blockscout" | "etherscan" | "blockscout-bytecode" - | "blockscout-ebd" | "whatsabi" | "manual" | "restored" diff --git a/src/components/debug/EvaluateModal.tsx b/src/components/debug/EvaluateModal.tsx index 1814755..12dc59f 100644 --- a/src/components/debug/EvaluateModal.tsx +++ b/src/components/debug/EvaluateModal.tsx @@ -35,7 +35,7 @@ interface EvalResult { } const LIVE_SESSION_BOOTSTRAP_TIMEOUT_MS = 120000; -const EVALUATION_TIMEOUT_MS = 15000; +const EVALUATION_TIMEOUT_MS = 45000; async function withTimeout( promise: Promise, diff --git a/src/components/execution-trace/traceAddressMaps.ts b/src/components/execution-trace/traceAddressMaps.ts index e5c9237..21d44dd 100644 --- a/src/components/execution-trace/traceAddressMaps.ts +++ b/src/components/execution-trace/traceAddressMaps.ts @@ -1,10 +1,5 @@ /** - * Address-map building helpers for the execution trace viewer. - * - * Extracted from useTraceState.ts to keep each module under 800 lines. - * All functions are pure or memoisation-friendly -- they receive data and - * return derived structures without side-effects. - */ + * Address-map building helpers for the execution trace viewer. */ import type { TraceRow } from "./traceTypes"; diff --git a/src/components/execution-trace/traceFrameHelpers.ts b/src/components/execution-trace/traceFrameHelpers.ts index 85546e2..f44008c 100644 --- a/src/components/execution-trace/traceFrameHelpers.ts +++ b/src/components/execution-trace/traceFrameHelpers.ts @@ -1,9 +1,6 @@ /** * Frame hierarchy, collapsed ranges, visibility, and visual-rail helpers - * for the execution trace viewer. - * - * Extracted from useTraceState.ts to keep each module under 800 lines. - */ + * for the execution trace viewer. */ import type { TraceRow, TraceFilters, FrameHierarchyEntry } from "./traceTypes"; diff --git a/src/components/explorer/StorageLayoutViewer.tsx b/src/components/explorer/StorageLayoutViewer.tsx index b893b53..7893221 100644 --- a/src/components/explorer/StorageLayoutViewer.tsx +++ b/src/components/explorer/StorageLayoutViewer.tsx @@ -12,6 +12,7 @@ import { StorageToolbar } from './StorageToolbar'; import { StorageTableView } from './StorageTableView'; import { TreePanel } from './TreePanel'; import { useStorageViewerState } from './useStorageViewerState'; +import { HeuristicLayoutBanner } from './storage-viewer/HeuristicLayoutBanner'; const StorageLayoutViewer: React.FC = () => { const state = useStorageViewerState(); @@ -19,6 +20,11 @@ const StorageLayoutViewer: React.FC = () => { // source/ABI data and would otherwise fall over with "No … API available". const explorerChains = React.useMemo(() => getExplorerChains(), []); + const [heuristicBannerDismissed, setHeuristicBannerDismissed] = React.useState(false); + React.useEffect(() => { + setHeuristicBannerDismissed(false); + }, [state.contractAddress, state.selectedChain?.id]); + return ( <>
@@ -50,6 +56,10 @@ const StorageLayoutViewer: React.FC = () => {
+ {state.layoutConfidence === 'heuristic' && !heuristicBannerDismissed ? ( + setHeuristicBannerDismissed(true)} /> + ) : null} + {state.hasData && ( = ({ className={`text-[10px] h-5 gap-1 ${ layoutConfidence === 'compiler' ? 'text-green-400 border-green-400/30' - : 'text-amber-400 border-amber-400/30' + : layoutConfidence === 'reconstructed' + ? 'text-amber-400 border-amber-400/30' + : 'text-orange-500 border-orange-500/40' }`} + title={ + layoutConfidence === 'heuristic' + ? 'Layout synthesized from Heimdall decompilation — fields may be mislabeled or missing. See the banner for caveats.' + : undefined + } > {layoutConfidence === 'compiler' ? ( ) : ( )} - {layoutConfidence === 'compiler' ? 'Compiler Layout' : 'Reconstructed'} + {layoutConfidence === 'compiler' + ? 'Compiler Layout' + : layoutConfidence === 'reconstructed' + ? 'Reconstructed' + : 'Heuristic'} )} {contractMeta?.proxyInfo?.proxyType === 'diamond' ? ( diff --git a/src/components/explorer/storage-viewer/HeuristicLayoutBanner.tsx b/src/components/explorer/storage-viewer/HeuristicLayoutBanner.tsx new file mode 100644 index 0000000..cff9167 --- /dev/null +++ b/src/components/explorer/storage-viewer/HeuristicLayoutBanner.tsx @@ -0,0 +1,32 @@ +import { ShieldWarning, X } from "@phosphor-icons/react"; +import { Button } from "../../ui/button"; + +interface Props { + onDismiss: () => void; +} + +export function HeuristicLayoutBanner({ onDismiss }: Props) { + return ( +
+ +
+
Heuristic layout — no verified source
+
+ This contract has no source on Sourcify. Slot labels and types shown + below were synthesized from Heimdall decompilation. Fields may be + mislabeled, and struct packing is not resolved. Treat values as + evidence, not ground truth. +
+
+ +
+ ); +} diff --git a/src/components/explorer/storage-viewer/fetchStorageLayout.ts b/src/components/explorer/storage-viewer/fetchStorageLayout.ts index 5bebb94..fa5daad 100644 --- a/src/components/explorer/storage-viewer/fetchStorageLayout.ts +++ b/src/components/explorer/storage-viewer/fetchStorageLayout.ts @@ -21,7 +21,7 @@ import { reconstructStorageLayout, reconstructBestStorageLayout } from '../../.. import { fetchSourcifyV2Cached, prewarmSourcifyCache } from '../../../utils/cache/sourcifyCache'; /** Confidence level for the layout source */ -export type LayoutConfidence = 'compiler' | 'reconstructed'; +export type LayoutConfidence = 'compiler' | 'reconstructed' | 'heuristic'; /** Result from fetchStorageLayout including confidence metadata */ export interface StorageLayoutResult { @@ -131,6 +131,58 @@ export interface FetchLayoutOptions { observedSlots?: Set; /** Additional addresses to try fetching layout from (e.g., diamond facets) */ fallbackAddresses?: string[]; + /** + * Heimdall-backed heuristic layout tier. When primary + AST + diamond all + * produce nothing, synthesize from a dump + optional decompilation. The caller + * provides fetchers so this module stays decoupled from the heimdall API. + * + * Note: the client passes `chainId` only. The bridge resolves the RPC URL + * server-side from an allowlist (Plan 2) and computes any bytecode hash + * itself. Never accept `rpcUrl` or `bytecodeHash` from the caller here — + * those are bridge-internal concerns (SSRF / cache-poisoning defense). + */ + heimdall?: { + chainId: number; + blockNumber?: number; + fetchStorageDump: (args: { + address: string; + chainId: number; + blockNumber?: number; + }) => Promise; + fetchDecompilation?: (args: { + address: string; + chainId: number; + }) => Promise; + }; +} + +async function tryHeuristicLayout( + address: string, + heimdall: NonNullable, +): Promise { + try { + const { synthesizeHeuristicLayout } = await import('../../../utils/heuristic-layout/heuristicLayout'); + const dump = await heimdall.fetchStorageDump({ + address, + chainId: heimdall.chainId, + blockNumber: heimdall.blockNumber, + }); + let decompilation; + if (heimdall.fetchDecompilation) { + decompilation = await heimdall.fetchDecompilation({ + address, + chainId: heimdall.chainId, + }).catch(() => undefined); + } + const layout = synthesizeHeuristicLayout({ dump, decompilation }); + if (layout.storage.length > 0) { + return { layout, confidence: 'heuristic' }; + } + } catch (err) { + // eslint-disable-next-line no-console + console.warn('[fetchStorageLayout] heuristic tier failed:', err); + } + return null; } /** @@ -247,7 +299,13 @@ export async function fetchStorageLayout( } // If no fallbacks, return whatever primary result we have (may be null) - if (!hasFallbacks) return primaryResult; + if (!hasFallbacks) { + if (!primaryResult && opts.heimdall) { + const heuristic = await tryHeuristicLayout(address, opts.heimdall); + if (heuristic) return heuristic; + } + return primaryResult; + } // 3. Diamond facet fallback: fetch layouts from facets with early exit. // Different facets may reference different versions of AppStorage, so @@ -322,6 +380,11 @@ export async function fetchStorageLayout( return richest; } - // No facet layouts found either; return primary if we have one + // No facet layouts found either; return primary if we have one. + // Heuristic fires as the last resort when nothing else produced a layout. + if (!primaryResult && opts.heimdall) { + const heuristic = await tryHeuristicLayout(address, opts.heimdall); + if (heuristic) return heuristic; + } return primaryResult; } diff --git a/src/components/explorer/storage-viewer/useStorageEvidence.ts b/src/components/explorer/storage-viewer/useStorageEvidence.ts index a51a41d..692b8db 100644 --- a/src/components/explorer/storage-viewer/useStorageEvidence.ts +++ b/src/components/explorer/storage-viewer/useStorageEvidence.ts @@ -583,6 +583,8 @@ export function useStorageEvidence() { sourceBundle?: { files: Record; contractName?: string; compilerVersion?: string }; /** Diamond facet addresses to try fetching layout from */ fallbackAddresses?: string[]; + /** Heuristic tier opt-in. See fetchStorageLayout.FetchLayoutOptions.heimdall. */ + heimdall?: import('./fetchStorageLayout').FetchLayoutOptions['heimdall']; }) => { abortRef.current?.abort(); abortRef.current = new AbortController(); @@ -629,6 +631,7 @@ export function useStorageEvidence() { sourceBundle: params.sourceBundle, observedSlots, fallbackAddresses: params.fallbackAddresses, + heimdall: params.heimdall, }, ); if (publicResult) { diff --git a/src/components/explorer/useStorageViewerState.ts b/src/components/explorer/useStorageViewerState.ts index d5f8879..85a6cbd 100644 --- a/src/components/explorer/useStorageViewerState.ts +++ b/src/components/explorer/useStorageViewerState.ts @@ -31,6 +31,8 @@ import type { import { shortHex } from './storageViewerHelpers'; import { useGridCharLimits } from './storageViewerHooks'; import { useStorageViewerData } from './useStorageViewerData'; +import { useHeimdallAvailability } from '../../utils/heimdall/useHeimdallAvailability'; +import { fetchHeimdallStorageDump, fetchHeimdallDecompilation } from '../../utils/heimdall/heimdallApi'; export function useStorageViewerState() { const location = useLocation(); @@ -91,6 +93,8 @@ export function useStorageViewerState() { readSlotFromRpc, } = useStorageEvidence(); + const heimdallAvailability = useHeimdallAvailability(); + const contextAbortRef = useRef(null); // Computed filter: during loading (before layout arrives), force 'all' so @@ -318,6 +322,14 @@ export function useStorageViewerState() { proxyType, sourceBundle, fallbackAddresses, + heimdall: heimdallAvailability.isAvailable + ? { + chainId, + blockNumber: undefined, + fetchStorageDump: fetchHeimdallStorageDump, + fetchDecompilation: fetchHeimdallDecompilation, + } + : undefined, }); setPostLoadResolving(true); @@ -383,7 +395,7 @@ export function useStorageViewerState() { performance.mark('storage-fetch-end'); performance.measure('storage-slot-table-paint', 'storage-fetch-start', 'storage-fetch-end'); - }, [contractAddress, chainId, sessionId, loadStorageForContract, seedFromLayout, seedDiamondNamespace, readSlotFromRpc, discovery]); + }, [contractAddress, chainId, sessionId, loadStorageForContract, seedFromLayout, seedDiamondNamespace, readSlotFromRpc, discovery, heimdallAvailability.isAvailable]); useEffect(() => { const params = new URLSearchParams(location.search); diff --git a/src/components/integrations/lifi-earn/DepositFlow.tsx b/src/components/integrations/lifi-earn/DepositFlow.tsx index e1e0f52..ecd59b2 100644 --- a/src/components/integrations/lifi-earn/DepositFlow.tsx +++ b/src/components/integrations/lifi-earn/DepositFlow.tsx @@ -1195,18 +1195,16 @@ export function DepositFlow({ type="button" disabled={isBusy} onClick={() => { - try { - if (!maxStr) return; - const maxVal = parseFloat(maxStr); - if (!Number.isFinite(maxVal) || maxVal <= 0) return; - const val = (maxVal * pct) / 100; - const dp = Math.min(selectedToken.decimals, 6); - setAmount(val.toFixed(dp)); - setSimResult(null); - setErrorMsg(null); - setFlowState("idle"); - setSpenderCheck({ status: "idle" }); - } catch {} + if (!maxStr) return; + const maxVal = parseFloat(maxStr); + if (!Number.isFinite(maxVal) || maxVal <= 0) return; + const val = (maxVal * pct) / 100; + const dp = Math.min(selectedToken.decimals, 6); + setAmount(val.toFixed(dp)); + setSimResult(null); + setErrorMsg(null); + setFlowState("idle"); + setSpenderCheck({ status: "idle" }); }} className="flex-1 rounded border border-border/40 bg-muted/30 py-1 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground disabled:opacity-50" > diff --git a/src/components/integrations/lifi-earn/LifiEarnPage.tsx b/src/components/integrations/lifi-earn/LifiEarnPage.tsx index ff06733..ac24618 100644 --- a/src/components/integrations/lifi-earn/LifiEarnPage.tsx +++ b/src/components/integrations/lifi-earn/LifiEarnPage.tsx @@ -12,10 +12,7 @@ import { IdleYieldBanner } from "./IdleYieldBanner"; import { useEarnPositions } from "./hooks/useEarnPositions"; import { usePositionVaults } from "./hooks/usePositionVaults"; import type { EarnVault } from "./types"; - -function truncateAddress(address: string): string { - return `${address.slice(0, 6)}…${address.slice(-4)}`; -} +import { shortenAddress as truncateAddress } from "../../shared/AddressDisplay"; const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/; diff --git a/src/components/integrations/lifi-earn/VaultList.tsx b/src/components/integrations/lifi-earn/VaultList.tsx index 9d8b241..fba6118 100644 --- a/src/components/integrations/lifi-earn/VaultList.tsx +++ b/src/components/integrations/lifi-earn/VaultList.tsx @@ -19,6 +19,7 @@ import { SUPPORTED_CHAINS } from "../../../utils/chains"; import ChainIcon from "../../icons/ChainIcon"; import { TokenIcon } from "./TokenIcon"; import type { EarnVault, VaultFilters } from "./types"; +import { shortenAddress as truncateAddr } from "../../shared/AddressDisplay"; import { useEarnVaults } from "./hooks/useEarnVaults"; import { useEarnChains } from "./hooks/useEarnChains"; import { useEarnProtocols } from "./hooks/useEarnProtocols"; @@ -630,10 +631,6 @@ function timeAgo(iso: string): string { return `${Math.floor(hrs / 24)}d ago`; } -function truncateAddr(addr: string): string { - return `${addr.slice(0, 6)}...${addr.slice(-4)}`; -} - function MiniSparkline({ points, color }: { points: (number | null)[]; color: string }) { const valid = points.filter((p): p is number => p !== null && p !== undefined); if (valid.length < 2) return null; diff --git a/src/components/integrations/lifi-earn/concierge/hooks/fetchAssetPrices.ts b/src/components/integrations/lifi-earn/concierge/hooks/fetchAssetPrices.ts index 5a0609d..cd4b4f0 100644 --- a/src/components/integrations/lifi-earn/concierge/hooks/fetchAssetPrices.ts +++ b/src/components/integrations/lifi-earn/concierge/hooks/fetchAssetPrices.ts @@ -2,11 +2,7 @@ // the asset could not be priced — keep amountUsd null, don't fabricate zero. import { formatUnits } from "viem"; import type { IdleAsset } from "../types"; - -const NATIVE_SENTINELS = new Set([ - "0x0000000000000000000000000000000000000000", - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", -]); +import { isNativeToken } from "../../../../../utils/addressConstants"; const LLAMA_CHAIN_SLUG: Record = { 1: "ethereum", @@ -84,12 +80,8 @@ function keyFor(chainId: number, address: string): string { return `${chainId}:${address.toLowerCase()}`; } -function isNative(address: string): boolean { - return NATIVE_SENTINELS.has(address.toLowerCase()); -} - function coinIdForAsset(asset: IdleAsset): string | null { - if (isNative(asset.token.address)) { + if (isNativeToken(asset.token.address)) { return NATIVE_COINGECKO_ID[asset.chainId] ?? null; } const slug = LLAMA_CHAIN_SLUG[asset.chainId]; diff --git a/src/components/integrations/lifi-earn/concierge/hooks/useIdleBalances.ts b/src/components/integrations/lifi-earn/concierge/hooks/useIdleBalances.ts index 533f377..3789da9 100644 --- a/src/components/integrations/lifi-earn/concierge/hooks/useIdleBalances.ts +++ b/src/components/integrations/lifi-earn/concierge/hooks/useIdleBalances.ts @@ -7,18 +7,7 @@ import { networkConfigManager } from "../../../../../config/networkConfig"; import { fetchAssetPrices, applyPricesToAssets } from "./fetchAssetPrices"; import type { EarnToken, EarnVault } from "../../types"; import type { IdleAsset } from "../types"; - -const NATIVE_SENTINELS = new Set([ - "0x0000000000000000000000000000000000000000", - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", -]); - -// Multicall3 is deployed at this address on virtually every EVM chain. -const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11" as const; - -function isNative(address: string): boolean { - return NATIVE_SENTINELS.has(address.toLowerCase()); -} +import { isNativeToken, MULTICALL3_ADDRESS } from "../../../../../utils/addressConstants"; export function useIdleBalances(targetAddress: string | null, perChainTimeoutMs = 8000) { @@ -175,8 +164,8 @@ async function scanSingleChain(args: { transport: http(rpcUrl), }); - const erc20s = tokens.filter((t) => !isNative(t.address)); - const nativeTokenMeta = tokens.find((t) => isNative(t.address)); + const erc20s = tokens.filter((t) => !isNativeToken(t.address)); + const nativeTokenMeta = tokens.find((t) => isNativeToken(t.address)); const multicallCalls = erc20s.map((tok) => ({ address: tok.address as `0x${string}`, diff --git a/src/components/integrations/lifi-earn/concierge/intent/IntentPanel.tsx b/src/components/integrations/lifi-earn/concierge/intent/IntentPanel.tsx index c7320c9..53d51db 100644 --- a/src/components/integrations/lifi-earn/concierge/intent/IntentPanel.tsx +++ b/src/components/integrations/lifi-earn/concierge/intent/IntentPanel.tsx @@ -24,6 +24,8 @@ import { useIdleBalances } from "../hooks/useIdleBalances"; import { useIntentParser } from "./hooks/useIntentParser"; import { useVaultsByIntent } from "./hooks/useVaultsByIntent"; import { useIntentRecommendation, buildRecommendation } from "./hooks/useIntentRecommendation"; +import { useLlmInvocation } from "../../../../../hooks/useLlmInvocation"; +import { useLlmConfig } from "../../../../../contexts/LlmConfigContext"; import type { ParsedIntent } from "./schema"; import type { EarnVault } from "../../types"; import type { IdleAsset, SelectedSource, VaultRecommendation } from "../types"; @@ -231,6 +233,10 @@ interface MultiRecResult { function useMultiAssetRecommendations( argsList: (Parameters[0] | null)[], ): MultiRecResult[] { + const { invoke } = useLlmInvocation(); + const { config } = useLlmConfig(); + const geminiModel = config.providers.gemini.model || "gemini-2.5-pro"; + const stableKey = useMemo( () => argsList.map((a) => @@ -244,7 +250,7 @@ function useMultiAssetRecommendations( const hasAnyArgs = argsList.some((a) => a != null); const query = useQuery<{ rec: VaultRecommendation | null; error: string | null }[]>({ - queryKey: ["multi-intent-rec", stableKey], + queryKey: ["multi-intent-rec", geminiModel, stableKey], enabled: hasAnyArgs, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, @@ -255,7 +261,7 @@ function useMultiAssetRecommendations( return Promise.all( argsList.map(async (args) => { if (!args || args.rankedVaults.length === 0) return { rec: null, error: null }; - const result = await buildRecommendation(args); + const result = await buildRecommendation(invoke, args, geminiModel); return { rec: result.recommendation, error: result.llmError }; }), ); diff --git a/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentParser.ts b/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentParser.ts index 49b7ef0..d7ca970 100644 --- a/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentParser.ts +++ b/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentParser.ts @@ -1,5 +1,6 @@ import { useMutation } from "@tanstack/react-query"; -import { postLlmRecommend } from "../../../earnApi"; +import { useLlmInvocation } from "../../../../../../hooks/useLlmInvocation"; +import { useLlmConfig } from "../../../../../../contexts/LlmConfigContext"; import { parsedIntentSchema, type ParsedIntent, DEFAULT_INTENT } from "../schema"; import type { EarnChainInfo, EarnProtocolInfo } from "../../../types"; @@ -19,62 +20,8 @@ export interface ParseIntentResult { rawText: string; } -export function useIntentParser() { - return useMutation({ - mutationFn: async ({ text, chains, protocols }) => { - const trimmed = text.trim(); - if (!trimmed) { - throw new Error("Please describe your yield goal."); - } - if (LLM_MODE === "off") { - return { intent: DEFAULT_INTENT, rawText: trimmed }; - } - - const request = buildParseRequest(trimmed, chains, protocols); - - let lastError: string | null = null; - for (let attempt = 0; attempt < 2; attempt++) { - try { - const raw = await postLlmRecommend(request); - const responseText = extractGeminiText(raw); - if (!responseText) throw new Error("empty LLM response"); - const json = safeParseJson(responseText); - if (!json) throw new Error("LLM did not return JSON"); - const result = parsedIntentSchema.safeParse(json); - if (!result.success) { - throw new Error( - `schema: ${result.error.issues[0]?.path.join(".") ?? "?"}: ${ - result.error.issues[0]?.message ?? "unknown" - }` - ); - } - return { intent: result.data, rawText: trimmed }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - lastError = msg; - // eslint-disable-next-line no-console - console.warn(`[intent-parser] attempt ${attempt + 1} failed:`, msg); - } - } - throw new Error(lastError ?? "Failed to parse intent"); - }, - }); -} - -function buildParseRequest( - userText: string, - chains: EarnChainInfo[], - protocols: EarnProtocolInfo[] -) { - // Ship a compact chain/protocol registry so Gemini can resolve "Arbitrum" - // to chainId 42161 and "Aave" to slug "aave-v3" without hallucinating. - const chainRegistry = chains.map((c) => ({ - chain_id: c.chainId, - name: c.name, - })); - const protocolRegistry = protocols.map((p) => p.name); - - const system = `You are a yield intent parser. The user will describe a DeFi yield goal in plain English. Convert their goal into a strict JSON object matching the required shape. +// The full system prompt is preserved verbatim — this is tuned prompt engineering. +const SYSTEM_PROMPT = `You are a yield intent parser. The user will describe a DeFi yield goal in plain English. Convert their goal into a strict JSON object matching the required shape. Rules: - Return ONLY JSON matching the shape below. No prose, no code fences, no commentary. @@ -127,93 +74,76 @@ DISAMBIGUATION EXAMPLES (for tricky fields only — use your judgment for object If the user gives a clearly non-yield or off-topic message, return all-null/default values.`; - const shape = { - target_symbol: "string | null", - my_assets: "boolean", - routing_mode: "'per-asset' | 'consolidate'", - target_chain_id: "number | null", - min_apy_pct: "number | null", - max_apy_pct: "number | null", - objective: "'safest' | 'highest' | 'balanced'", - min_tvl_usd: "number | null", - include_protocols: "string[]", - exclude_protocols: "string[]", - result_count: "number (1-4) | null", - }; - - const payload = { - user_text: userText, - chain_registry: chainRegistry, - protocol_registry: protocolRegistry, - required_output_shape: shape, - }; - - return { - contents: [ - { - role: "user", - parts: [ - { text: system }, - { - text: - "INPUT:\n```json\n" + - JSON.stringify(payload, null, 2) + - "\n```\n\nReturn ONLY the JSON object.", - }, - ], - }, - ], - generationConfig: { - responseMimeType: "application/json", - temperature: 0.1, - }, - }; -} - -function extractGeminiText(raw: unknown): string | null { - // Gemini 3 Pro can return multi-part content with `thought: true` parts - // before the answer — concatenate every non-thought text part. - try { - const r = raw as { - candidates?: Array<{ - content?: { - parts?: Array<{ text?: string; thought?: boolean }>; - }; - }>; - }; - const parts = r.candidates?.[0]?.content?.parts ?? []; - const joined = parts - .filter((p) => !p.thought && typeof p.text === "string") - .map((p) => p.text ?? "") - .join("") - .trim(); - return joined.length > 0 ? joined : null; - } catch { - return null; - } -} +export function useIntentParser() { + const { invoke } = useLlmInvocation(); + const { config } = useLlmConfig(); + const geminiModel = config.providers.gemini.model || "gemini-2.5-pro"; -function safeParseJson(text: string): unknown { - try { - return JSON.parse(text); - } catch { - const stripped = text - .replace(/^```(?:json)?/i, "") - .replace(/```$/i, "") - .trim(); - try { - return JSON.parse(stripped); - } catch { - const first = stripped.indexOf("{"); - const last = stripped.lastIndexOf("}"); - if (first >= 0 && last > first) { - try { - return JSON.parse(stripped.slice(first, last + 1)); - } catch { - /* fall through */ - } + return useMutation({ + mutationFn: async ({ text, chains, protocols }) => { + const trimmed = text.trim(); + if (!trimmed) { + throw new Error("Please describe your yield goal."); + } + if (LLM_MODE === "off") { + return { intent: DEFAULT_INTENT, rawText: trimmed }; } - return null; - } - } + + // Ship a compact chain/protocol registry so Gemini can resolve "Arbitrum" + // to chainId 42161 and "Aave" to slug "aave-v3" without hallucinating. + const chainRegistry = chains.map((c) => ({ + chain_id: c.chainId, + name: c.name, + })); + const protocolRegistry = protocols.map((p) => p.name); + + const shape = { + target_symbol: "string | null", + my_assets: "boolean", + routing_mode: "'per-asset' | 'consolidate'", + target_chain_id: "number | null", + min_apy_pct: "number | null", + max_apy_pct: "number | null", + objective: "'safest' | 'highest' | 'balanced'", + min_tvl_usd: "number | null", + include_protocols: "string[]", + exclude_protocols: "string[]", + result_count: "number (1-4) | null", + }; + + const payload = { + user_text: trimmed, + chain_registry: chainRegistry, + protocol_registry: protocolRegistry, + required_output_shape: shape, + }; + + const userContent = + "INPUT:\n```json\n" + + JSON.stringify(payload, null, 2) + + "\n```\n\nReturn ONLY the JSON object."; + + // Note: the old code set responseMimeType:"application/json" and temperature:0.1 in + // generationConfig; the shared transport omits those. stripJsonEnvelope (in the shared + // hook) handles any markdown fencing Gemini may add. Temperature defaults higher, so + // outputs are slightly less deterministic — accepted trade-off for the shared layer. + // Note: the old extractGeminiText filtered thought:true parts; extractText in the shared + // hook concatenates all parts. In practice Gemini 2.5 Pro emits no thought parts unless + // thinkingConfig is set (it isn't here), so this is a no-op. + const res = await invoke({ + task: "yield-intent-parse", + provider: "gemini", + model: geminiModel, + messages: [ + { role: "system", content: SYSTEM_PROMPT }, + { role: "user", content: userContent }, + ], + responseFormat: "json", + schema: parsedIntentSchema, + maxRetries: 2, + }); + if (!res.parsed) throw new Error("LLM did not return a valid intent"); + return { intent: res.parsed, rawText: trimmed }; + }, + }); } diff --git a/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentRecommendation.ts b/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentRecommendation.ts index eb0f40a..b3fd0a3 100644 --- a/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentRecommendation.ts +++ b/src/components/integrations/lifi-earn/concierge/intent/hooks/useIntentRecommendation.ts @@ -1,6 +1,7 @@ import { useQuery } from "@tanstack/react-query"; -import { postLlmRecommend } from "../../../earnApi"; -import { llmRecommendationSchema } from "../../schema"; +import { useLlmInvocation } from "../../../../../../hooks/useLlmInvocation"; +import { useLlmConfig } from "../../../../../../contexts/LlmConfigContext"; +import { llmRecommendationSchema, type LlmRecommendationResponse } from "../../schema"; import { DEFAULT_CONFIG } from "../../types"; import type { VaultRecommendation, @@ -74,6 +75,8 @@ interface IntentRecommendationResult { llmError: string | null; } +type InvokeFn = ReturnType["invoke"]; + /** * Produces a single VaultRecommendation (best / safest / alts + rationale) for * an intent-filtered vault set. @@ -87,10 +90,15 @@ interface IntentRecommendationResult { export function useIntentRecommendation( args: IntentRecommendationArgs | null ): IntentRecommendationResult & { isLoading: boolean; isFetching: boolean; refetch: () => void } { + const { invoke } = useLlmInvocation(); + const { config } = useLlmConfig(); + const geminiModel = config.providers.gemini.model || "gemini-2.5-pro"; + const query = useQuery({ queryKey: [ "intent-recommendation", LLM_MODE, + geminiModel, args?.synthChainId ?? 0, args?.synthTokenAddress ?? "", intentCacheKey(args?.intent), @@ -104,7 +112,7 @@ export function useIntentRecommendation( refetchOnWindowFocus: false, queryFn: async (): Promise => { if (!args) return { recommendation: null, llmError: null }; - return buildRecommendation(args); + return buildRecommendation(invoke, args, geminiModel); }, }); @@ -118,7 +126,9 @@ export function useIntentRecommendation( } export async function buildRecommendation( - args: IntentRecommendationArgs + invoke: InvokeFn, + args: IntentRecommendationArgs, + model: string = "gemini-2.5-pro", ): Promise { const { synthChainId, synthTokenAddress, intent, rankedVaults } = args; const maxCandidates = @@ -143,108 +153,109 @@ export async function buildRecommendation( }; } - const request = buildGeminiIntentRequest(intent, candidates, args.walletAssets, args.sourceTokenSymbol, args.sourceChainId); - let lastError: string | null = null; - - for (let attempt = 0; attempt < 3; attempt++) { - if (attempt > 0) await new Promise((r) => setTimeout(r, 800 * attempt)); - try { - const raw = await postLlmRecommend(request); - const text = extractGeminiText(raw); - if (!text) throw new Error("empty LLM response"); - const json = safeParseJson(text); - if (!json) throw new Error("LLM did not return JSON"); - const result = llmRecommendationSchema.safeParse(json); - if (!result.success) { - throw new Error( - `schema: ${result.error.issues[0]?.path.join(".") ?? "?"}: ${ - result.error.issues[0]?.message ?? "unknown" - }` - ); - } - const rec = result.data.recommendations[0]; - if (!rec) throw new Error("LLM returned empty recommendations array"); - - const slugToVault = new Map(candidates.map((v) => [v.slug, v])); - const resolve = ( - p: { vault_slug: string; rationale: string } | null - ): RecommendationPick | null => { - if (!p) return null; - const v = slugToVault.get(p.vault_slug); - if (!v) return null; - return { vaultSlug: p.vault_slug, vault: v, rationale: p.rationale }; - }; + const { system, userText } = buildIntentPrompt(intent, candidates, args.walletAssets, args.sourceTokenSymbol, args.sourceChainId); - const bestPick = resolve(rec.best_pick); - const safestPick = resolve(rec.safest_pick); - const alternatives = rec.alternatives - .map((a) => resolve(a)) - .filter((p): p is RecommendationPick => p !== null); - - // If the LLM returned slugs outside the candidate list, both picks - // resolve to null. Fall back to rules so users get usable results. - if (!bestPick && !safestPick && candidates.length > 0) { - return { - recommendation: rulesFallback(synthChainId, synthTokenAddress, intent, candidates), - llmError: "LLM returned unknown vault slugs — used rules fallback", - }; - } + let llmError: string | null = null; + try { + // Note: no responseMimeType:"application/json" — shared hook's stripJsonEnvelope handles + // markdown-fenced output. No temperature:0.2 — uses provider defaults (slightly less + // deterministic). No thought-part filtering — Gemini 2.5 Pro without thinkingConfig + // doesn't emit thought parts in practice. maxRetries:2 instead of 3; shared hook only + // retries schema_invalid + network/rate_limit/provider_down, not every error class. + const res = await invoke({ + task: "yield-recommendation", + provider: "gemini", + model, + messages: [ + { role: "system", content: system }, + { role: "user", content: userText }, + ], + responseFormat: "json", + schema: llmRecommendationSchema, + maxRetries: 2, + }); + if (!res.parsed) throw new Error("recommendation parse failed"); + const rec = res.parsed.recommendations[0]; + if (!rec) throw new Error("LLM returned empty recommendations array"); + + const slugToVault = new Map(candidates.map((v) => [v.slug, v])); + const resolve = ( + p: { vault_slug: string; rationale: string } | null + ): RecommendationPick | null => { + if (!p) return null; + const v = slugToVault.get(p.vault_slug); + if (!v) return null; + return { vaultSlug: p.vault_slug, vault: v, rationale: p.rationale }; + }; - // Enforce distinct best/safest when candidate list allows — mirrors - // enforceDistinctPicks in idle-sweep fallback. - let finalSafest = safestPick; - if ( - bestPick && - safestPick && - bestPick.vaultSlug === safestPick.vaultSlug && - candidates.length > 1 - ) { - const runner = candidates.find((v) => v.slug !== bestPick.vaultSlug); - if (runner) { - finalSafest = { - vaultSlug: runner.slug, - vault: runner, - rationale: `Next-most-conservative alternative after the best pick on ${runner.protocol.name}.`, - }; - } else { - finalSafest = null; - } - } - - // Deduplicate alternatives against best/safest picks - const dedupedAlts: RecommendationPick[] = []; - const seenPicks: RecommendationPick[] = []; - if (bestPick) seenPicks.push(bestPick); - if (finalSafest) seenPicks.push(finalSafest); - for (const alt of alternatives) { - if (seenPicks.some((sp) => isDuplicatePick(sp, alt))) continue; - if (dedupedAlts.some((da) => isDuplicatePick(da, alt))) continue; - dedupedAlts.push(alt); - } + const bestPick = resolve(rec.best_pick); + const safestPick = resolve(rec.safest_pick); + const alternatives = rec.alternatives + .map((a) => resolve(a)) + .filter((p): p is RecommendationPick => p !== null); + // If the LLM returned slugs outside the candidate list, both picks + // resolve to null. Fall back to rules so users get usable results. + if (!bestPick && !safestPick && candidates.length > 0) { return { - recommendation: { - forChainId: synthChainId, - forTokenAddress: synthTokenAddress.toLowerCase(), - bestPick, - safestPick: finalSafest, - alternatives: dedupedAlts, - source: "ai", - topRationale: rec.best_pick?.rationale ?? "", - }, - llmError: null, + recommendation: rulesFallback(synthChainId, synthTokenAddress, intent, candidates), + llmError: "LLM returned unknown vault slugs — used rules fallback", }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - lastError = msg; - // eslint-disable-next-line no-console - console.warn(`[intent-rec] LLM attempt ${attempt + 1} failed:`, msg); } + + // Enforce distinct best/safest when candidate list allows — mirrors + // enforceDistinctPicks in idle-sweep fallback. + let finalSafest = safestPick; + if ( + bestPick && + safestPick && + bestPick.vaultSlug === safestPick.vaultSlug && + candidates.length > 1 + ) { + const runner = candidates.find((v) => v.slug !== bestPick.vaultSlug); + if (runner) { + finalSafest = { + vaultSlug: runner.slug, + vault: runner, + rationale: `Next-most-conservative alternative after the best pick on ${runner.protocol.name}.`, + }; + } else { + finalSafest = null; + } + } + + // Deduplicate alternatives against best/safest picks + const dedupedAlts: RecommendationPick[] = []; + const seenPicks: RecommendationPick[] = []; + if (bestPick) seenPicks.push(bestPick); + if (finalSafest) seenPicks.push(finalSafest); + for (const alt of alternatives) { + if (seenPicks.some((sp) => isDuplicatePick(sp, alt))) continue; + if (dedupedAlts.some((da) => isDuplicatePick(da, alt))) continue; + dedupedAlts.push(alt); + } + + return { + recommendation: { + forChainId: synthChainId, + forTokenAddress: synthTokenAddress.toLowerCase(), + bestPick, + safestPick: finalSafest, + alternatives: dedupedAlts, + source: "ai", + topRationale: rec.best_pick?.rationale ?? "", + }, + llmError: null, + }; + } catch (err) { + llmError = err instanceof Error ? err.message : String(err); + // eslint-disable-next-line no-console + console.warn("[intent-rec] LLM failed, falling back to rules:", llmError); } return { recommendation: rulesFallback(synthChainId, synthTokenAddress, intent, candidates), - llmError: lastError, + llmError, }; } @@ -308,7 +319,17 @@ function rulesFallback( }; } -function buildGeminiIntentRequest(intent: ParsedIntent, candidates: EarnVault[], walletAssets: IdleAsset[], sourceTokenSymbol?: string, sourceChainId?: number) { +/** + * Builds the system prompt and user text for the intent recommendation LLM call. + * Extracted from the old buildGeminiIntentRequest — all string content is preserved verbatim. + */ +function buildIntentPrompt( + intent: ParsedIntent, + candidates: EarnVault[], + walletAssets: IdleAsset[], + sourceTokenSymbol?: string, + sourceChainId?: number, +): { system: string; userText: string } { const system = `You are a DeFi yield strategist with deep knowledge of vault mechanics, protocol risk, and yield sustainability. You evaluate vaults the way a seasoned DeFi portfolio manager would — not just by raw numbers, but by understanding what drives those numbers and whether they'll last. You will be given: @@ -456,26 +477,12 @@ ENTRY COST FRAMEWORK: }, }; - return { - contents: [ - { - role: "user", - parts: [ - { text: fullSystem }, - { - text: - "INPUT:\n" + - JSON.stringify(userPayload) + - "\n\nReturn ONLY the JSON object matching required_output_shape. No prose, no code fences.", - }, - ], - }, - ], - generationConfig: { - responseMimeType: "application/json", - temperature: 0.2, - }, - }; + const userText = + "INPUT:\n" + + JSON.stringify(userPayload) + + "\n\nReturn ONLY the JSON object matching required_output_shape. No prose, no code fences."; + + return { system: fullSystem, userText }; } function intentCacheKey(intent: ParsedIntent | undefined): string { @@ -494,53 +501,6 @@ function intentCacheKey(intent: ParsedIntent | undefined): string { ].join("|"); } -function extractGeminiText(raw: unknown): string | null { - try { - const r = raw as { - candidates?: Array<{ - content?: { - parts?: Array<{ text?: string; thought?: boolean }>; - }; - }>; - }; - const parts = r.candidates?.[0]?.content?.parts ?? []; - const joined = parts - .filter((p) => !p.thought && typeof p.text === "string") - .map((p) => p.text ?? "") - .join("") - .trim(); - return joined.length > 0 ? joined : null; - } catch { - return null; - } -} - -function safeParseJson(text: string): unknown { - try { - return JSON.parse(text); - } catch { - const stripped = text - .replace(/^```(?:json)?/i, "") - .replace(/```$/i, "") - .trim(); - try { - return JSON.parse(stripped); - } catch { - // Thinking models sometimes prepend prose before the JSON object. - const first = stripped.indexOf("{"); - const last = stripped.lastIndexOf("}"); - if (first >= 0 && last > first) { - try { - return JSON.parse(stripped.slice(first, last + 1)); - } catch { - /* fall through */ - } - } - return null; - } - } -} - function formatApy(apy: number | null): string { if (apy == null) return "—"; return `${apy.toFixed(2)}%`; diff --git a/src/components/integrations/lifi-earn/hooks/useTokenBalance.ts b/src/components/integrations/lifi-earn/hooks/useTokenBalance.ts index e07f411..d1bbab6 100644 --- a/src/components/integrations/lifi-earn/hooks/useTokenBalance.ts +++ b/src/components/integrations/lifi-earn/hooks/useTokenBalance.ts @@ -2,21 +2,12 @@ import { useQuery } from "@tanstack/react-query"; import { ethers } from "ethers"; import { networkConfigManager } from "../../../../config/networkConfig"; import { SUPPORTED_CHAINS } from "../../../../utils/chains"; +import { isNativeToken } from "../../../../utils/addressConstants"; const ERC20_BALANCE_ABI = [ "function balanceOf(address owner) view returns (uint256)", ]; -// Matches the NATIVE_ADDRESSES set in DepositFlow — keep in sync. -const NATIVE_SENTINELS = new Set([ - "0x0000000000000000000000000000000000000000", - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", -]); - -function isNative(address: string): boolean { - return NATIVE_SENTINELS.has(address.toLowerCase()); -} - async function fetchBalance( tokenAddress: string, ownerAddress: string, @@ -33,7 +24,7 @@ async function fetchBalance( } const provider = new ethers.providers.JsonRpcProvider(resolution.url); - if (isNative(tokenAddress)) { + if (isNativeToken(tokenAddress)) { const raw: ethers.BigNumber = await provider.getBalance(ownerAddress); return raw.toString(); } diff --git a/src/components/integrations/lifi-earn/txUtils.ts b/src/components/integrations/lifi-earn/txUtils.ts index 5e7a36a..fab3db7 100644 --- a/src/components/integrations/lifi-earn/txUtils.ts +++ b/src/components/integrations/lifi-earn/txUtils.ts @@ -1,7 +1,4 @@ -export function shortAddress(addr: string): string { - if (!addr || addr.length < 10) return addr; - return `${addr.slice(0, 6)}…${addr.slice(-4)}`; -} +export { shortenAddress as shortAddress } from "../../shared/AddressDisplay"; /** * Turn a thrown error into a one-liner a user can act on. @@ -67,11 +64,4 @@ export function formatTxError(err: unknown): string { return "Transaction failed"; } -export const NATIVE_ADDRESSES = new Set([ - "0x0000000000000000000000000000000000000000", - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", -]); - -export function isNativeToken(addr: string): boolean { - return NATIVE_ADDRESSES.has(addr.toLowerCase()); -} +export { isNativeToken } from "../../../utils/addressConstants"; diff --git a/src/components/llm/LlmConsentModal.tsx b/src/components/llm/LlmConsentModal.tsx new file mode 100644 index 0000000..8f88b78 --- /dev/null +++ b/src/components/llm/LlmConsentModal.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "../ui/dialog"; +import { Button } from "../ui/button"; +import { useLlmConsent } from "../../hooks/useLlmConsent"; + +interface Props { + open: boolean; + onAcknowledge: () => void; + onClose: () => void; + providerName?: string; +} + +export const LlmConsentModal: React.FC = ({ open, onAcknowledge, onClose, providerName }) => { + const { requestAck } = useLlmConsent(); + const handleAck = () => { + requestAck(); + onAcknowledge(); + }; + return ( + { if (!v) onClose(); }}> + + + Sending data to an LLM + + Hexkit is about to send transaction and contract data to {providerName ?? "a language model"} for analysis. + + +
+

+ When you run simple or complex transaction analysis with BYOK keys (Anthropic, OpenAI, or a custom + endpoint), the following data leaves your browser: +

+
    +
  • Decoded trace rows (SSTOREs, SLOADs, calls, events, balance deltas) for the selected transaction
  • +
  • Verified or decompiled Solidity source for contracts on the execution path
  • +
  • The prompt we author (methodology instructions)
  • +
+

+ BYOK runs stay private: reports generated with your own keys are not shared with + the hexkit cache unless you explicitly opt in per run. +

+

+ The free Gemini 3.1 Pro Preview default routes through hexkit's proxy; by using it you + agree that resulting reports may be cached and shown to other hexkit users. +

+

+ You can change providers or disable LLM calls in LLM Settings at any time. +

+
+
+ + +
+
+
+ ); +}; diff --git a/src/components/llm/LlmDestinationChip.tsx b/src/components/llm/LlmDestinationChip.tsx new file mode 100644 index 0000000..0ae9269 --- /dev/null +++ b/src/components/llm/LlmDestinationChip.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import type { LlmProvider } from "../../utils/llm/types"; + +interface Props { + provider: LlmProvider; + mode: "default" | "byok"; + shared: boolean; +} + +const PROVIDER_LABEL: Record = { + anthropic: "Anthropic", + openai: "OpenAI", + gemini: "Gemini", + custom: "Custom", +}; + +export const LlmDestinationChip: React.FC = ({ provider, mode, shared }) => ( + + sending to {PROVIDER_LABEL[provider]} + {mode === "byok" ? · your key : null} + · + + {shared ? "shared to cache" : "not shared"} + + +); diff --git a/src/components/settings/LlmSettingsPanel.tsx b/src/components/settings/LlmSettingsPanel.tsx new file mode 100644 index 0000000..269f426 --- /dev/null +++ b/src/components/settings/LlmSettingsPanel.tsx @@ -0,0 +1,154 @@ +import React, { useState } from "react"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { useLlmConfig } from "../../contexts/LlmConfigContext"; +import type { LlmProvider } from "../../utils/llm/types"; + +interface Props { + onClose: () => void; +} + +const MODEL_HINTS: Record = { + anthropic: ["claude-opus-4-7", "claude-sonnet-4-6"], + openai: ["gpt-5.4", "gpt-5.4-mini"], + gemini: ["gemini-2.5-pro", "gemini-3.1-pro-preview"], + custom: [], +}; + +const PROVIDERS: LlmProvider[] = ["gemini", "anthropic", "openai", "custom"]; + +const PROVIDER_LABELS: Record = { + gemini: "Gemini", + anthropic: "Anthropic", + openai: "OpenAI", + custom: "Custom", +}; + +const LlmSettingsPanel: React.FC = ({ onClose }) => { + const { config, saveConfig } = useLlmConfig(); + const [draft, setDraft] = useState(config); + const [activeTab, setActiveTab] = useState(config.defaultProvider); + + const save = () => { + saveConfig(draft); + onClose(); + }; + + return ( +
+

+ Default provider is Gemini via the hexkit proxy. Bring your own key for Anthropic, OpenAI, + or a custom endpoint. +

+
+ {PROVIDERS.map((p) => ( + + ))} +
+ {PROVIDERS.map((p) => ( + + ))} +
+ + +
+
+ ); +}; + +export default LlmSettingsPanel; diff --git a/src/components/settings/RpcSettingsPanel.tsx b/src/components/settings/RpcSettingsPanel.tsx new file mode 100644 index 0000000..ac03019 --- /dev/null +++ b/src/components/settings/RpcSettingsPanel.tsx @@ -0,0 +1,354 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + networkConfigManager, + type ExplorerKeyMode, + type RpcProviderMode, + isValidRpcUrl, +} from "../../config/networkConfig"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { Badge } from "../ui/badge"; +import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; +import { RadioGroup, RadioGroupItem } from "../ui/radio-group"; +import { Checkbox } from "../ui/checkbox"; +import { Eye, EyeSlash, CheckCircle, WarningCircle, CircleNotch } from "@phosphor-icons/react"; +import { cn } from "@/lib/utils"; + +interface RpcSettingsPanelProps { + onClose: () => void; + onSaved?: () => void; +} + +type FormState = { + mode: RpcProviderMode; + alchemyApiKey: string; + infuraProjectId: string; + customRpcUrl: string; + etherscanKeyMode: ExplorerKeyMode; + rememberPersonalEtherscanKey: boolean; + etherscanApiKey: string; +}; + +type ErrorKey = RpcProviderMode | "ETHERSCAN"; +type AutoSaveState = "idle" | "saving" | "saved" | "error"; + +const RpcSettingsPanel: React.FC = ({ onClose, onSaved }) => { + const [formState, setFormState] = useState(() => { + const config = networkConfigManager.getConfig(); + return { + mode: config.rpcMode ?? "DEFAULT", + alchemyApiKey: config.alchemyApiKey ?? "", + infuraProjectId: config.infuraProjectId ?? "", + customRpcUrl: config.customRpcUrl ?? "", + etherscanKeyMode: config.etherscanKeyMode ?? "default", + rememberPersonalEtherscanKey: config.rememberPersonalEtherscanKey ?? false, + etherscanApiKey: config.etherscanApiKey ?? "", + }; + }); + const [errors, setErrors] = useState>>({}); + const [autoSaveState, setAutoSaveState] = useState("saved"); + const [showAlchemyKey, setShowAlchemyKey] = useState(false); + const [showInfuraKey, setShowInfuraKey] = useState(false); + const [showEtherscanKey, setShowEtherscanKey] = useState(false); + const autoSaveTimer = useRef(null); + const initialSyncRef = useRef(true); + + const formStateRef = useRef(formState); + formStateRef.current = formState; + useEffect(() => { + return () => { + if (autoSaveTimer.current) { + window.clearTimeout(autoSaveTimer.current); + autoSaveTimer.current = null; + const fs = formStateRef.current; + networkConfigManager.saveConfig({ + rpcMode: fs.mode, + alchemyApiKey: fs.alchemyApiKey.trim(), + infuraProjectId: fs.infuraProjectId.trim(), + customRpcUrl: fs.customRpcUrl.trim(), + etherscanKeyMode: fs.etherscanKeyMode, + rememberPersonalEtherscanKey: fs.rememberPersonalEtherscanKey, + etherscanApiKey: fs.etherscanApiKey.trim(), + }); + } + }; + }, []); + + const computeErrors = (state: FormState) => { + const nextErrors: typeof errors = {}; + if (state.mode === "ALCHEMY" && !state.alchemyApiKey.trim()) { + nextErrors.ALCHEMY = "API key required"; + } + if (state.mode === "INFURA" && !state.infuraProjectId.trim()) { + nextErrors.INFURA = "Project ID required"; + } + if (state.mode === "CUSTOM") { + if (!state.customRpcUrl.trim()) { + nextErrors.CUSTOM = "RPC URL required"; + } else if (!isValidRpcUrl(state.customRpcUrl)) { + nextErrors.CUSTOM = "Enter a valid HTTP(s) URL"; + } + } + if (state.etherscanKeyMode === "personal" && !state.etherscanApiKey.trim()) { + nextErrors.ETHERSCAN = "Personal API key required"; + } + return nextErrors; + }; + + useEffect(() => { + const nextErrors = computeErrors(formState); + setErrors(nextErrors); + + if (initialSyncRef.current) { + initialSyncRef.current = false; + setAutoSaveState(Object.keys(nextErrors).length ? "error" : "saved"); + return; + } + + if (autoSaveTimer.current) { + window.clearTimeout(autoSaveTimer.current); + } + setAutoSaveState("saving"); + + autoSaveTimer.current = window.setTimeout(() => { + autoSaveTimer.current = null; + networkConfigManager.saveConfig({ + rpcMode: formState.mode, + alchemyApiKey: formState.alchemyApiKey.trim(), + infuraProjectId: formState.infuraProjectId.trim(), + customRpcUrl: formState.customRpcUrl.trim(), + etherscanKeyMode: formState.etherscanKeyMode, + rememberPersonalEtherscanKey: formState.rememberPersonalEtherscanKey, + etherscanApiKey: formState.etherscanApiKey.trim(), + }); + const hasErrors = Object.keys(nextErrors).length > 0; + setAutoSaveState(hasErrors ? "error" : "saved"); + if (onSaved) onSaved(); + }, 500); + + return () => { + if (autoSaveTimer.current) { + window.clearTimeout(autoSaveTimer.current); + } + }; + }, [formState, onSaved]); + + return ( +
+

+ Personal settings stay in your browser; the app default explorer key stays server-side. +

+ + + setFormState((prev) => ({ ...prev, mode: value as RpcProviderMode })) + } + > + + + Default + + + Alchemy + + + Infura + + + Custom + + + + +
+ +
+ {formState.mode === "DEFAULT" ? ( + + ) : formState.mode === "ALCHEMY" ? ( + + setFormState((prev) => ({ ...prev, alchemyApiKey: e.target.value })) + } + placeholder="Enter API key..." + className={cn("flex-1", errors.ALCHEMY && "border-destructive")} + /> + ) : formState.mode === "INFURA" ? ( + + setFormState((prev) => ({ ...prev, infuraProjectId: e.target.value })) + } + placeholder="Enter project ID..." + className={cn("flex-1", errors.INFURA && "border-destructive")} + /> + ) : ( + + setFormState((prev) => ({ ...prev, customRpcUrl: e.target.value })) + } + placeholder="https://your-node.example.com" + className={cn("flex-1", errors.CUSTOM && "border-destructive")} + /> + )} + {formState.mode !== "DEFAULT" && formState.mode !== "CUSTOM" && ( + + )} +
+ {(errors.ALCHEMY || errors.INFURA || errors.CUSTOM) && ( +

+ {errors.ALCHEMY || errors.INFURA || errors.CUSTOM} +

+ )} +
+ +
+
+ + + Optional + +
+ + setFormState((prev) => ({ ...prev, etherscanKeyMode: value as ExplorerKeyMode })) + } + className="gap-2" + > + + + + + {formState.etherscanKeyMode === "personal" ? ( +
+
+ + setFormState((prev) => ({ ...prev, etherscanApiKey: e.target.value })) + } + placeholder="Enter API key..." + className={cn("flex-1", errors.ETHERSCAN && "border-destructive")} + /> + +
+ + {errors.ETHERSCAN && ( +

{errors.ETHERSCAN}

+ )} +

+ Personal keys stored in the browser are still visible to browser scripts and + extensions on this device. +

+
+ ) : ( +

+ Default mode keeps the shared explorer key off the client and works across supported + Etherscan-style networks. +

+ )} +
+ +
+
+ {autoSaveState === "saving" && ( + <> + + Saving... + + )} + {autoSaveState === "saved" && ( + <> + + Saved + + )} + {autoSaveState === "error" && ( + <> + + Check fields + + )} +
+ +
+
+ ); +}; + +export default RpcSettingsPanel; diff --git a/src/components/shared/AddressDisplay.tsx b/src/components/shared/AddressDisplay.tsx index d7c7b81..80a8d06 100644 --- a/src/components/shared/AddressDisplay.tsx +++ b/src/components/shared/AddressDisplay.tsx @@ -5,7 +5,7 @@ * Only the pure helper functions remain. */ -const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; +import { ZERO_ADDRESS } from '../../utils/addressConstants'; /** Pure utility: truncate an address string consistently */ export function shortenAddress( diff --git a/src/components/simple-grid/GridContext.tsx b/src/components/simple-grid/GridContext.tsx index db73f46..50ba34e 100644 --- a/src/components/simple-grid/GridContext.tsx +++ b/src/components/simple-grid/GridContext.tsx @@ -56,7 +56,6 @@ export type AbiSourceType = | "blockscout" | "etherscan" | "blockscout-bytecode" - | "blockscout-ebd" | "whatsabi" | "manual" | "restored" diff --git a/src/components/simple-grid/buildGridContextValue.ts b/src/components/simple-grid/buildGridContextValue.ts index 3106375..94b598e 100644 --- a/src/components/simple-grid/buildGridContextValue.ts +++ b/src/components/simple-grid/buildGridContextValue.ts @@ -1,6 +1,5 @@ /** * buildGridContextValue – assembles the GridContext value object and style constants. - * Extracted from SimpleGridMain.tsx (pure structural split – no behaviour changes). */ import type React from "react"; import type { ReactNode } from "react"; diff --git a/src/components/simple-grid/hooks/useContractState.ts b/src/components/simple-grid/hooks/useContractState.ts index a5dac10..d785663 100644 --- a/src/components/simple-grid/hooks/useContractState.ts +++ b/src/components/simple-grid/hooks/useContractState.ts @@ -1,9 +1,6 @@ /** * useContractState – manages contract address, network, ABI fetching, - * saved-contracts storage, and all closely related state. - * - * Extracted from SimpleGridMain.tsx (pure structural split – no behaviour changes). - */ + * saved-contracts storage, and all closely related state. */ import { useState, useCallback, useRef } from "react"; import { ethers } from "ethers"; import type { Chain, ContractInfo } from "../../../types"; diff --git a/src/components/simple-grid/hooks/useDiamondState.ts b/src/components/simple-grid/hooks/useDiamondState.ts index 1fee2be..d86e926 100644 --- a/src/components/simple-grid/hooks/useDiamondState.ts +++ b/src/components/simple-grid/hooks/useDiamondState.ts @@ -1,8 +1,5 @@ /** - * useDiamondState – manages diamond facet selection, sidebar, and loading. - * - * Extracted from SimpleGridMain.tsx (pure structural split – no behaviour changes). - */ + * useDiamondState – manages diamond facet selection, sidebar, and loading. */ import { useState, useCallback, useMemo } from "react"; import { ethers } from "ethers"; import type { DiamondFacet } from "../../../utils/diamondFacetFetcher"; diff --git a/src/components/simple-grid/hooks/useFunctionState.ts b/src/components/simple-grid/hooks/useFunctionState.ts index 71e7d6e..3bf5fee 100644 --- a/src/components/simple-grid/hooks/useFunctionState.ts +++ b/src/components/simple-grid/hooks/useFunctionState.ts @@ -1,9 +1,6 @@ /** * useFunctionState – manages function selection, inputs, calldata generation - * and decoded calldata state. - * - * Extracted from SimpleGridMain.tsx (pure structural split – no behaviour changes). - */ + * and decoded calldata state. */ import { useState, useCallback, useMemo, useRef } from "react"; import { ethers } from "ethers"; import type { ABIInput } from "../../ContractInputComponent"; diff --git a/src/components/simple-grid/hooks/useRestorationEffects.ts b/src/components/simple-grid/hooks/useRestorationEffects.ts index b627dec..c330f49 100644 --- a/src/components/simple-grid/hooks/useRestorationEffects.ts +++ b/src/components/simple-grid/hooks/useRestorationEffects.ts @@ -1,7 +1,6 @@ /** * useRestorationEffects - Handles restoring state from initialContractData * and SimulationContext contractContext. - * Extracted from SimpleGridMain.tsx (pure structural split -- no behaviour changes). */ import { useEffect, type MutableRefObject } from "react"; import { ethers } from "ethers"; diff --git a/src/components/simple-grid/hooks/useSharedEffects.ts b/src/components/simple-grid/hooks/useSharedEffects.ts index fbeb65c..8b2cad8 100644 --- a/src/components/simple-grid/hooks/useSharedEffects.ts +++ b/src/components/simple-grid/hooks/useSharedEffects.ts @@ -2,7 +2,6 @@ * useSharedEffects – shared side-effect logic extracted from SimpleGridMain.tsx. * Handles CSS injection, simulation result expansion, auto-decode, auto-calldata, * auto-save, and auto-function-type selection. - * Pure structural split – no behaviour changes. */ import { useEffect } from "react"; import { ethers } from "ethers"; diff --git a/src/components/simple-grid/hooks/useSimulationState.tsx b/src/components/simple-grid/hooks/useSimulationState.tsx index 86e3bd3..1f9d05c 100644 --- a/src/components/simple-grid/hooks/useSimulationState.tsx +++ b/src/components/simple-grid/hooks/useSimulationState.tsx @@ -1,9 +1,6 @@ /** * useSimulationState – manages simulation execution, results, stack frames, - * and rendering helpers. - * - * Extracted from SimpleGridMain.tsx (pure structural split – no behaviour changes). - */ + * and rendering helpers. */ import React, { useState, useCallback, useMemo, type ReactNode } from "react"; import { ethers } from "ethers"; import type { SimulationResult, TransactionRequest } from "../../../types/transaction"; diff --git a/src/components/simple-grid/hooks/useTokenState.ts b/src/components/simple-grid/hooks/useTokenState.ts index 5992e73..a3e3146 100644 --- a/src/components/simple-grid/hooks/useTokenState.ts +++ b/src/components/simple-grid/hooks/useTokenState.ts @@ -1,8 +1,5 @@ /** - * useTokenState – manages token detection flags and token info. - * - * Extracted from SimpleGridMain.tsx (pure structural split – no behaviour changes). - */ + * useTokenState – manages token detection flags and token info. */ import { useState } from "react"; export function useTokenState() { diff --git a/src/components/simple-grid/hooks/useWalletHelpers.ts b/src/components/simple-grid/hooks/useWalletHelpers.ts index 5cf3e23..1d640af 100644 --- a/src/components/simple-grid/hooks/useWalletHelpers.ts +++ b/src/components/simple-grid/hooks/useWalletHelpers.ts @@ -1,8 +1,5 @@ /** - * useWalletHelpers – wallet chain-id helpers and ethers provider factory. - * - * Extracted from SimpleGridMain.tsx (pure structural split – no behaviour changes). - */ + * useWalletHelpers – wallet chain-id helpers and ethers provider factory. */ import { useCallback } from "react"; import { ethers } from "ethers"; import { SUPPORTED_CHAINS } from "../../../utils/chains"; diff --git a/src/components/simple-grid/layout/AbiUploadSection.tsx b/src/components/simple-grid/layout/AbiUploadSection.tsx index cfb85ce..7d2c120 100644 --- a/src/components/simple-grid/layout/AbiUploadSection.tsx +++ b/src/components/simple-grid/layout/AbiUploadSection.tsx @@ -1,6 +1,5 @@ /** * AbiUploadSection - ABI error display and manual ABI upload modal. - * Extracted from ContractColumn.tsx to reduce file size. */ import React from "react"; import { XCircleIcon } from "../../icons/IconLibrary"; diff --git a/src/components/simple-grid/layout/CalldataSection.tsx b/src/components/simple-grid/layout/CalldataSection.tsx index a1313bc..787d0f4 100644 --- a/src/components/simple-grid/layout/CalldataSection.tsx +++ b/src/components/simple-grid/layout/CalldataSection.tsx @@ -1,6 +1,5 @@ /** * CalldataSection - Dynamic calldata display. - * Extracted from GridLayout.tsx lines 2508-2567. */ import React from "react"; import { CopyButton } from "../../ui/copy-button"; diff --git a/src/components/simple-grid/layout/ContractColumn.tsx b/src/components/simple-grid/layout/ContractColumn.tsx index 25cca1f..f0f93e3 100644 --- a/src/components/simple-grid/layout/ContractColumn.tsx +++ b/src/components/simple-grid/layout/ContractColumn.tsx @@ -1,6 +1,5 @@ /** * ContractColumn - Contract input, saved contracts, contract info card. - * Extracted from GridLayout.tsx lines 309-1335. * * Sub-components extracted for maintainability: * - AbiUploadSection: ABI error display + manual ABI upload modal diff --git a/src/components/simple-grid/layout/ContractInfoCard.tsx b/src/components/simple-grid/layout/ContractInfoCard.tsx index e4b0cfb..34ad0c3 100644 --- a/src/components/simple-grid/layout/ContractInfoCard.tsx +++ b/src/components/simple-grid/layout/ContractInfoCard.tsx @@ -1,6 +1,5 @@ /** * ContractInfoCard - Displays contract metadata card when contractInfo is available. - * Extracted from ContractColumn.tsx to reduce file size. * * Shows: chain icon, contract name, ABI source badge, proxy badge, * token type detection, symbol/decimals, and read/write function counts. diff --git a/src/components/simple-grid/layout/ContractPreviewCard.tsx b/src/components/simple-grid/layout/ContractPreviewCard.tsx index 8d208d3..70356b2 100644 --- a/src/components/simple-grid/layout/ContractPreviewCard.tsx +++ b/src/components/simple-grid/layout/ContractPreviewCard.tsx @@ -1,7 +1,6 @@ /** * ContractPreviewCard - Token/contract preview card shown before full * contractInfo is available, plus the loading skeleton card. - * Extracted from ContractColumn.tsx to reduce file size. */ import React from "react"; import { GemIcon } from "../../icons/IconLibrary"; diff --git a/src/components/simple-grid/layout/DiamondPopup.tsx b/src/components/simple-grid/layout/DiamondPopup.tsx index 00e18e3..58f6cb7 100644 --- a/src/components/simple-grid/layout/DiamondPopup.tsx +++ b/src/components/simple-grid/layout/DiamondPopup.tsx @@ -1,6 +1,5 @@ /** * DiamondPopup - Diamond contract popup wrapper. - * Extracted from GridLayout.tsx lines 4259-4273. */ import React from "react"; import DiamondContractPopup from "../../DiamondContractPopup"; diff --git a/src/components/simple-grid/layout/ExecutionSection.tsx b/src/components/simple-grid/layout/ExecutionSection.tsx index 687c718..f4205ec 100644 --- a/src/components/simple-grid/layout/ExecutionSection.tsx +++ b/src/components/simple-grid/layout/ExecutionSection.tsx @@ -1,6 +1,5 @@ /** * ExecutionSection - Function execution button, gas estimation, result display, simulation insights. - * Extracted from GridLayout.tsx lines 2569-3604. */ import React from "react"; import { AnimatedPlayIcon } from "../../icons/IconLibrary"; diff --git a/src/components/simple-grid/layout/FunctionParamsSection.tsx b/src/components/simple-grid/layout/FunctionParamsSection.tsx index c517013..dca4137 100644 --- a/src/components/simple-grid/layout/FunctionParamsSection.tsx +++ b/src/components/simple-grid/layout/FunctionParamsSection.tsx @@ -1,6 +1,5 @@ /** * FunctionParamsSection - Enhanced function parameters input. - * Extracted from GridLayout.tsx lines 1954-2506. */ import React from "react"; import { diff --git a/src/components/simple-grid/layout/FunctionResultSection.tsx b/src/components/simple-grid/layout/FunctionResultSection.tsx index 822c6dd..dd74c31 100644 --- a/src/components/simple-grid/layout/FunctionResultSection.tsx +++ b/src/components/simple-grid/layout/FunctionResultSection.tsx @@ -1,6 +1,5 @@ /** * FunctionResultSection - Raw calldata mode: input, decoded display, execution, results. - * Extracted from GridLayout.tsx lines 3608-3879. */ import React from "react"; import { AnimatedPlayIcon, Loader2Icon } from "../../icons/IconLibrary"; diff --git a/src/components/simple-grid/layout/FunctionSearchSection.tsx b/src/components/simple-grid/layout/FunctionSearchSection.tsx index 1e1d571..b2b37ec 100644 --- a/src/components/simple-grid/layout/FunctionSearchSection.tsx +++ b/src/components/simple-grid/layout/FunctionSearchSection.tsx @@ -1,6 +1,5 @@ /** * FunctionSearchSection - Search popup + search results. - * Extracted from GridLayout.tsx lines 1643-1903. */ import React from "react"; import { SearchIcon } from "../../icons/IconLibrary"; diff --git a/src/components/simple-grid/layout/FunctionSelectSection.tsx b/src/components/simple-grid/layout/FunctionSelectSection.tsx index f155e43..955bc9b 100644 --- a/src/components/simple-grid/layout/FunctionSelectSection.tsx +++ b/src/components/simple-grid/layout/FunctionSelectSection.tsx @@ -1,6 +1,5 @@ /** * FunctionSelectSection - Function dropdown selector. - * Extracted from GridLayout.tsx lines 1905-1952. */ import React from "react"; import { SearchIcon } from "../../icons/IconLibrary"; diff --git a/src/components/simple-grid/layout/FunctionTypeSection.tsx b/src/components/simple-grid/layout/FunctionTypeSection.tsx index 149c279..bd52c5e 100644 --- a/src/components/simple-grid/layout/FunctionTypeSection.tsx +++ b/src/components/simple-grid/layout/FunctionTypeSection.tsx @@ -1,6 +1,5 @@ /** * FunctionTypeSection - Diamond facet controls, function mode selection, and function type tabs. - * Extracted from GridLayout.tsx lines 1337-1641. */ import React from "react"; import { diff --git a/src/components/simple-grid/layout/OverridesSidebar.tsx b/src/components/simple-grid/layout/OverridesSidebar.tsx index 753c9be..06ab967 100644 --- a/src/components/simple-grid/layout/OverridesSidebar.tsx +++ b/src/components/simple-grid/layout/OverridesSidebar.tsx @@ -1,6 +1,5 @@ /** * OverridesSidebar - Right column simulation overrides panel. - * Extracted from GridLayout.tsx lines 4243-4255. */ import React from "react"; import SimulationOverridesPanel from "../../SimulationOverridesPanel"; diff --git a/src/components/simple-grid/tokenDetection.ts b/src/components/simple-grid/tokenDetection.ts index 92fc17c..5faff85 100644 --- a/src/components/simple-grid/tokenDetection.ts +++ b/src/components/simple-grid/tokenDetection.ts @@ -14,7 +14,6 @@ import { detectTokenType } from "./tokenDetection/functionDetection"; /** * Detect and fetch token information for a contract. - * Extracted from SimpleGridMain to reduce file size. */ export async function detectAndFetchTokenInfo( deps: TokenDetectionDeps, diff --git a/src/components/simple-grid/types.ts b/src/components/simple-grid/types.ts index 707795b..213b67e 100644 --- a/src/components/simple-grid/types.ts +++ b/src/components/simple-grid/types.ts @@ -1,6 +1,5 @@ /** * Types and interfaces for SimpleGridUI sub-components. - * Extracted from SimpleGridUI.tsx during structural refactor. */ import type { ReactNode } from "react"; import type { ethers } from "ethers"; diff --git a/src/components/simple-grid/utils.ts b/src/components/simple-grid/utils.ts index 42e15b7..095aef7 100644 --- a/src/components/simple-grid/utils.ts +++ b/src/components/simple-grid/utils.ts @@ -43,12 +43,8 @@ export const stringifyResultData = (value: any): string => { try { return JSON.stringify(value, replacer, 2); - } catch (error) { - try { - return String(value); - } catch { - return ""; - } + } catch { + try { return String(value); } catch { return ""; } } }; diff --git a/src/components/simulation-results/ResultsHeader.tsx b/src/components/simulation-results/ResultsHeader.tsx index 1f4c119..3cf4b6d 100644 --- a/src/components/simulation-results/ResultsHeader.tsx +++ b/src/components/simulation-results/ResultsHeader.tsx @@ -3,6 +3,7 @@ import { ArrowLeft, ShareNetwork, ArrowsClockwise, DownloadSimple } from "@phosp import { Button } from "../ui/button"; import { HoverCard, HoverCardTrigger, HoverCardContent } from "../ui/hover-card"; import { DebugPillButton } from "./DebugPillButton"; +import { SummarizeButton } from "../tx-analysis/SummarizeButton"; import type { DebugPrepState } from "../../types/debug"; interface ResultsHeaderProps { @@ -21,6 +22,7 @@ interface ResultsHeaderProps { hasLiveDebugSession: boolean; debugPrepState: DebugPrepState; cancelDebugPrep: () => void; + handleSummarize?: () => void; } export const ResultsHeader: React.FC = ({ @@ -39,6 +41,7 @@ export const ResultsHeader: React.FC = ({ hasLiveDebugSession, debugPrepState, cancelDebugPrep, + handleSummarize, }) => { return (
@@ -75,6 +78,9 @@ export const ResultsHeader: React.FC = ({ Export EDB test script + {handleSummarize ? ( + + ) : null} +
+
+ {verdict.deepDive ? ( + <> +

Verdict upgrade: {verdict.deepDive.verdictUpgrade}

+ {verdict.deepDive.additionalRiskBound ? ( +

+ Additional risk bound: {verdict.deepDive.additionalRiskBound.upperBoundEth} ETH + {" "}— {verdict.deepDive.additionalRiskBound.rationale} +

+ ) : null} +
    + {verdict.deepDive.trustBoundaries.map((tb) => ( +
  • +

    {tb.contract} ({tb.sourceQuality})

    +
      {tb.findings.map((f, i) =>
    • {f}
    • )}
    +
  • + ))} +
+ + ) : ( +

No deep dive yet.

+ )} +
+
+ ); +}; diff --git a/src/components/tx-analysis/EvidenceList.tsx b/src/components/tx-analysis/EvidenceList.tsx new file mode 100644 index 0000000..0b1449f --- /dev/null +++ b/src/components/tx-analysis/EvidenceList.tsx @@ -0,0 +1,87 @@ +import React from "react"; +import type { EvidencePacket } from "../../utils/tx-analysis/types"; + +interface Props { + packet: EvidencePacket; +} + +const Section: React.FC<{ title: string; count: number; truncated: boolean; children: React.ReactNode }> = ({ + title, count, truncated, children, +}) => ( +
+
+

{title} ({count})

+ {truncated ? truncated : null} +
+ {children} +
+); + +const short = (v: string) => (v.length > 14 ? `${v.slice(0, 10)}…${v.slice(-4)}` : v); + +export const EvidenceList: React.FC = ({ packet }) => { + return ( +
+
+ + + + {packet.writes.map((w) => ( + + + + + + + + ))} + +
ContractSlotBeforeAfterSource
{short(w.contract)}{w.slot}{w.valueBefore ?? "∅"}{w.valueAfter}{w.sourceFile ? `${w.sourceFile}:${w.sourceLine ?? "?"}` : "—"}
+
+ +
+ + + + {packet.reads.map((r) => ( + + + + + + + ))} + +
ContractSlotValueFollows
{short(r.contract)}{r.slot}{r.value}{r.followsWriteId ?? "—"}
+
+ +
+
    + {packet.triggers.map((t) => ( +
  • + {t.kind} {t.function ?? t.selector ?? "?"} → {short(t.contract)} +
  • + ))} +
+
+ +
+
    + {packet.profit.map((p) => ( +
  • {p.asset} {p.direction === "in" ? "+" : "-"}{p.delta} @ {short(p.holder)}
  • + ))} +
+
+ + {packet.heuristics.length > 0 ? ( +
+
    + {packet.heuristics.map((h, i) => ( +
  • {h.name}: {h.reason}
  • + ))} +
+
+ ) : null} +
+ ); +}; diff --git a/src/components/tx-analysis/HackAnalysisPanel.tsx b/src/components/tx-analysis/HackAnalysisPanel.tsx new file mode 100644 index 0000000..a9a214d --- /dev/null +++ b/src/components/tx-analysis/HackAnalysisPanel.tsx @@ -0,0 +1,306 @@ +import React from "react"; +import { HEXKIT_CSS, MicroLabel } from "./hexkitTheme"; +import type { HackAnalysis, Incident } from "../../utils/hack-analysis/types"; + +export interface HackAnalysisPanelProps { + analysis: HackAnalysis; + analogs?: Incident[]; +} + +const truncAddr = (addr: string): string => + addr.length > 10 ? `${addr.slice(0, 6)}…${addr.slice(-4)}` : addr; + +const verdictPill = ( + verdict: HackAnalysis["verdict"], +): { className: string; color: string } => { + if (verdict === "HACK_CONFIRMED") return { className: "hk-pill pill-error", color: "var(--error)" }; + if (verdict === "HACK_LIKELY") return { className: "hk-pill pill-warning", color: "var(--warning)" }; + if (verdict === "LOOKS_BENIGN") return { className: "hk-pill pill-success", color: "var(--success)" }; + return { className: "hk-pill pill-default", color: "var(--text-tertiary)" }; +}; + +export function HackAnalysisPanel({ analysis, analogs }: HackAnalysisPanelProps): React.ReactElement { + const pill = verdictPill(analysis.verdict); + const confidencePct = Math.round(analysis.confidence * 100); + + const analogsToShow = analogs?.filter((a) => analysis.analogIncidentIds.includes(a.id)) ?? []; + + const sortedSteps = [...analysis.attackSteps].sort((a, b) => a.order - b.order); + + return ( +
+ +
+
+ + {analysis.verdict} + + + {confidencePct}% confidence + +
+ +

+ {analysis.headline} +

+ +
+ {/* LEFT RAIL */} +
+ {/* Core Contradiction */} +
+ Core Contradiction +

+ {analysis.coreContradiction} +

+
+ + {/* Exploit Classes */} + {analysis.exploitClasses.length > 0 && ( +
+ Exploit Classes +
+ {analysis.exploitClasses.map((ec) => ( +
+
+ {ec.class} + + {Math.round(ec.confidence * 100)}% + +
+

+ {ec.rationale} +

+
+ ))} +
+
+ )} + + {/* Entities */} + {analysis.entities.length > 0 && ( +
+ Entities +
+ {analysis.entities.map((ent) => ( +
+
+ {ent.role} +
+
+ {truncAddr(ent.address)} +
+ {ent.label} +
+ ))} +
+
+ )} + + {/* Analogs */} + {analogsToShow.length > 0 && ( +
+ Similar Incidents +
+ {analogsToShow.map((inc) => ( +
+ + {inc.name ?? inc.id} + + {inc.chain && ( + {inc.chain} + )} + {inc.protocol && ( + {inc.protocol} + )} +
+ ))} +
+
+ )} + + {/* Caveats */} + {analysis.caveats.length > 0 && ( +
+ Caveats +
    + {analysis.caveats.map((c, i) => ( +
  • + {c} +
  • + ))} +
+
+ )} +
+ + {/* RIGHT RAIL */} +
+ {/* Attack Steps */} + {sortedSteps.length > 0 && ( +
+ Attack Steps +
    + {sortedSteps.map((step) => ( +
  1. + + {step.order} + +
    + + {step.label} + +

    {step.detail}

    + {step.evidenceIds.length > 0 && ( +
    + {step.evidenceIds.map((eid) => ( + + {eid} + + ))} +
    + )} +
    +
  2. + ))} +
+
+ )} + + {/* Fund Flow */} + {analysis.fundFlow.length > 0 && ( +
+ Fund Flow +
+ {analysis.fundFlow.map((ff, i) => ( +
+ {ff.fromLabel} + + {ff.toLabel} + {(ff.amountHuman || ff.tokenSymbol) && ( + + {[ff.amountHuman, ff.tokenSymbol].filter(Boolean).join(" ")} + + )} +
+ ))} +
+
+ )} + + {/* Missing Evidence */} + {analysis.missingEvidence.length > 0 && ( +
+ Missing Evidence +
    + {analysis.missingEvidence.map((m, i) => ( +
  • + {m} +
  • + ))} +
+
+ )} +
+
+
+
+ ); +} diff --git a/src/components/tx-analysis/HackTriagePanel.tsx b/src/components/tx-analysis/HackTriagePanel.tsx new file mode 100644 index 0000000..3758b7c --- /dev/null +++ b/src/components/tx-analysis/HackTriagePanel.tsx @@ -0,0 +1,298 @@ +import React from "react"; +import { HEXKIT_CSS, MicroLabel } from "./hexkitTheme"; +import { classBitsToLabels, type TriageResult } from "../../utils/hack-analysis/triage/cofhe"; + +export interface HackTriagePanelProps { + verdict: TriageResult; + txHash: `0x${string}` | null; + handles: { classBits: string; severity: string } | null; + contractAddress: `0x${string}`; +} + +function shortHex(hex: string, head = 10, tail = 8): string { + if (hex.length <= head + tail + 2) return hex; + return `${hex.slice(0, head)}…${hex.slice(-tail)}`; +} + +function ConsumerContractSnippet({ + contractAddress, +}: { + contractAddress: `0x${string}`; +}): React.ReactElement { + const [open, setOpen] = React.useState(false); + const code = `// Any Sepolia contract can gate on this encrypted verdict. +// Only permit holders can decrypt — the chain never sees it in plaintext. +import { IHackTriage } from "./IHackTriage.sol"; +import { euint8, ebool, FHE } from "@fhenixprotocol/cofhe-contracts/FHE.sol"; + +contract Escrow { + IHackTriage constant TRIAGE = IHackTriage(${contractAddress}); + uint8 constant MAX_SAFE_SEVERITY = 4; + + function release(address user, uint256 amount) external { + (, uint256 sevHandle, ) = TRIAGE.latest(user); + euint8 severity = euint8.wrap(sevHandle); + + // Comparison runs under FHE. The bool is also encrypted. + ebool safe = FHE.lte(severity, FHE.asEuint8(MAX_SAFE_SEVERITY)); + FHE.allow(safe, msg.sender); + FHE.req(safe); // revert if severity > threshold, never revealing which + _transfer(user, amount); + } +}`; + + return ( +
+ + {open ? ( +
+          {code}
+        
+ ) : null} +
+ ); +} + +/** Severity bar gauge: 0–9 scale rendered as a simple SVG bar. */ +function SeverityGauge({ value, max = 9 }: { value: number; max?: number }): React.ReactElement { + const clamped = Math.max(0, Math.min(value, max)); + const pct = max > 0 ? clamped / max : 0; + const barWidth = 120; + const fillWidth = Math.round(pct * barWidth); + // Purple fill to stay visually distinct from green/red cleartext path + const fillColor = "#a78bfa"; + + return ( +
+ + + {fillWidth > 0 && ( + + )} + + + {clamped} / 10 + +
+ ); +} + +export function HackTriagePanel({ + verdict, + txHash, + handles, + contractAddress, +}: HackTriagePanelProps): React.ReactElement { + const labels = classBitsToLabels(verdict.classBits); + + return ( +
+ +
+
+ + 🔒 Verified under FHE + +
+ +
+ {/* LEFT RAIL */} +
+ {/* Class Labels */} +
+ Encrypted Rule Classes +
+ {labels.length > 0 ? ( + labels.map((label) => ( + + {label} + + )) + ) : ( + + No encrypted rules fired + + )} +
+
+ + {/* Severity */} +
+ Encrypted severity (decrypted locally) +
+ +
+
+
+ + {/* RIGHT RAIL */} +
+
+ What the chain stored +

+ The severity gauge you see above was decrypted in your browser under your permit. + On-chain, only these ciphertext handles exist. Any Sepolia contract can read them + and run comparisons under FHE — no one needs to decrypt to make a decision. +

+
+ + {handles ? ( +
+ Ciphertext handles +
+ classBits + + {shortHex(handles.classBits)} + + severity + + {shortHex(handles.severity)} + +
+
+ ) : null} + +
+ For downstream contracts +

+ An escrow, throttle, or insurance pool can gate on this without ever learning the + verdict: +

+ +
+ + {txHash ? ( +
+ On-chain record + +
+ ) : null} +
+
+
+
+ ); +} diff --git a/src/components/tx-analysis/SummarizeButton.tsx b/src/components/tx-analysis/SummarizeButton.tsx new file mode 100644 index 0000000..904d2e1 --- /dev/null +++ b/src/components/tx-analysis/SummarizeButton.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { Detective } from "@phosphor-icons/react"; +import { Button } from "../ui/button"; +import { HoverCard, HoverCardTrigger, HoverCardContent } from "../ui/hover-card"; + +interface Props { + onSummarize: () => void; + disabled?: boolean; +} + +export const SummarizeButton: React.FC = ({ onSummarize, disabled }) => ( + + + + + Summarize this transaction with Tx Captain + +); diff --git a/src/components/tx-analysis/SummaryCard.tsx b/src/components/tx-analysis/SummaryCard.tsx new file mode 100644 index 0000000..1cfce34 --- /dev/null +++ b/src/components/tx-analysis/SummaryCard.tsx @@ -0,0 +1,491 @@ +import React from "react"; +import { HexKitRoot, HKI, MicroLabel, shortAddress } from "./hexkitTheme"; +import type { + EvidencePacket, + TriggerEvidence, + ProfitEvidence, + Verdict, +} from "../../utils/tx-analysis/types"; + +const TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; +const APPROVAL_TOPIC0 = "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"; + +type ActivityKind = "transfer" | "eth" | "call" | "event" | "approve"; +type ActivityFilter = "all" | "transfer" | "call" | "event" | "approve"; + +interface Activity { + key: string; + kind: ActivityKind; + dir?: "in" | "out"; + label: string; + sub?: string; + value?: string; + valueSub?: string; +} + +const topicToAddress = (topic: string | undefined): string | null => { + if (!topic) return null; + const clean = topic.toLowerCase().replace(/^0x/, ""); + if (clean.length < 40) return null; + return `0x${clean.slice(-40)}`; +}; + +const eqAddr = (a: string | null, b: string | null): boolean => + !!(a && b && a.toLowerCase() === b.toLowerCase()); + +function triggerToActivity(t: TriggerEvidence, packet: EvidencePacket): Activity { + const key = t.id; + if (t.kind === "LOG") { + const topic0 = (t.logTopics[0] ?? "").toLowerCase(); + const isTransfer = topic0 === TRANSFER_TOPIC0; + const isApproval = topic0 === APPROVAL_TOPIC0; + if (isTransfer) { + const fromAddr = topicToAddress(t.logTopics[1]); + const toAddr = topicToAddress(t.logTopics[2]); + const dir: "in" | "out" | undefined = eqAddr(toAddr, packet.from) + ? "in" + : eqAddr(fromAddr, packet.from) + ? "out" + : undefined; + const amount = t.args.find((a) => /value|amount|wad/i.test(a.name))?.value ?? null; + return { + key, + kind: "transfer", + dir, + label: `Transfer ${shortAddress(fromAddr ?? "?")} → ${shortAddress(toAddr ?? "?")}`, + sub: `${shortAddress(t.contract)} · topic0 ${topic0.slice(0, 10)}…`, + value: amount ?? t.id, + }; + } + if (isApproval) { + const ownerAddr = topicToAddress(t.logTopics[1]); + const spenderAddr = topicToAddress(t.logTopics[2]); + return { + key, + kind: "approve", + label: `Approval ${shortAddress(ownerAddr ?? "?")} → ${shortAddress(spenderAddr ?? "?")}`, + sub: `${shortAddress(t.contract)} · topic0 ${topic0.slice(0, 10)}…`, + value: t.id, + }; + } + return { + key, + kind: "event", + label: t.function ?? `Log (${t.logTopics.length} topic${t.logTopics.length === 1 ? "" : "s"})`, + sub: `${shortAddress(t.contract)}${topic0 ? ` · topic0 ${topic0.slice(0, 10)}…` : ""}`, + value: t.id, + }; + } + // CALL / DELEGATECALL / STATICCALL / CREATE / CREATE2 + const fnLabel = t.function ?? t.selector ?? `${t.kind.toLowerCase()}()`; + return { + key, + kind: "call", + label: fnLabel, + sub: `${t.kind} · ${shortAddress(t.contract)}`, + value: t.selector ?? t.id, + }; +} + +function profitToActivity(p: ProfitEvidence): Activity { + const kind: ActivityKind = p.asset === "ETH" ? "eth" : "transfer"; + const assetLabel = p.asset === "ETH" ? "ETH" : p.asset; + return { + key: p.id, + kind, + dir: p.direction, + label: `${p.direction === "in" ? "Received" : "Sent"} ${assetLabel}`, + sub: `holder ${shortAddress(p.holder)}${p.token ? ` · token ${shortAddress(p.token)}` : ""}`, + value: p.delta, + }; +} + +export function deriveActivities(packet: EvidencePacket): Activity[] { + const acts: Activity[] = []; + for (const t of packet.triggers) acts.push(triggerToActivity(t, packet)); + for (const p of packet.profit) acts.push(profitToActivity(p)); + return acts; +} + +const activityTypeStyle = (kind: ActivityKind, dir?: "in" | "out") => { + if (kind === "transfer" || kind === "eth") { + const out = dir === "out"; + return { + bg: out ? "rgba(245,158,11,0.14)" : "rgba(34,197,94,0.14)", + border: out ? "rgba(245,158,11,0.30)" : "rgba(34,197,94,0.30)", + color: out ? "#facc15" : "#bbf7d0", + label: kind === "eth" ? "ETH" : "TRANSFER", + icon: out ? : , + }; + } + if (kind === "approve") { + return { bg: "rgba(255,255,255,0.06)", border: "rgba(255,255,255,0.18)", color: "#e5e5e5", label: "APPROVE", icon: null }; + } + if (kind === "call") { + return { bg: "rgba(255,255,255,0.10)", border: "rgba(255,255,255,0.25)", color: "#ffffff", label: "CALL", icon: null }; + } + return { bg: "#262626", border: "rgba(255,255,255,0.10)", color: "#a1a1aa", label: "EVENT", icon: null }; +}; + +const verdictStatusPill = ( + v: Verdict["verdict"], + reverted: boolean, +): { className: string; label: string } => { + if (reverted) return { className: "hk-pill pill-warning", label: "REVERTED" }; + if (v === "CONFIRMED") return { className: "hk-pill pill-error", label: "CONFIRMED EXPLOIT" }; + if (v === "OPEN") return { className: "hk-pill pill-warning", label: "OPEN" }; + return { className: "hk-pill pill-success", label: "LIKELY BENIGN" }; +}; + +const verdictTypeLabel = ( + v: Verdict, + reverted: boolean, +): { type: string; category: string } => { + if (reverted) { + return { type: "Reverted transaction", category: "No state changed — cannot be an exploit" }; + } + if (v.verdict === "CONFIRMED") return { type: "Exploit detected", category: "Security · High confidence" }; + if (v.verdict === "OPEN") return { type: "Needs review", category: "Security · Inconclusive" }; + return { type: "Routine transaction", category: "Evidence insufficient for exploit claim" }; +}; + +const pickHeadline = (verdict: Verdict, packet: EvidencePacket): string => { + if (verdict.coreContradiction?.actual) return verdict.coreContradiction.actual; + const firstCausal = verdict.causalChain[0]?.description; + if (firstCausal) return firstCausal; + return `Transaction from ${shortAddress(packet.from)} to ${shortAddress(packet.to)}${packet.success ? "" : " (reverted)"}.`; +}; + +const pickNarrative = (verdict: Verdict): string | null => { + if (verdict.coreContradiction) { + return `Expected: ${verdict.coreContradiction.expected}`; + } + if (verdict.causalChain.length > 1) { + return verdict.causalChain + .slice(1, 3) + .map((s) => s.description) + .join(" "); + } + if (verdict.missingEvidence.length > 0) { + return verdict.missingEvidence[0]; + } + return null; +}; + +interface SummaryCardProps { + verdict: Verdict; + packet: EvidencePacket; + txHash: string | null; + chainName?: string; + busy?: boolean; + onDeepScan?: () => void; + onCopyHash?: () => void; + error?: { title: string; detail?: string } | null; +} + +export const SummaryCard: React.FC = ({ + verdict, + packet, + txHash, + chainName, + busy, + onDeepScan, + onCopyHash, + error, +}) => { + const [filter, setFilter] = React.useState("all"); + const activities = React.useMemo(() => deriveActivities(packet), [packet]); + + const total = activities.length; + const counts = { + transfer: activities.filter((a) => a.kind === "transfer" || a.kind === "eth").length, + call: activities.filter((a) => a.kind === "call").length, + event: activities.filter((a) => a.kind === "event").length, + approve: activities.filter((a) => a.kind === "approve").length, + }; + const visible = activities.filter((a) => { + if (filter === "all") return true; + if (filter === "transfer") return a.kind === "transfer" || a.kind === "eth"; + return a.kind === filter; + }); + + const reverted = packet.success === false; + const status = verdictStatusPill(verdict.verdict, reverted); + const typeLabel = verdictTypeLabel(verdict, reverted); + const headline = reverted + ? packet.revertReason + ? `Reverted: ${packet.revertReason}` + : pickHeadline(verdict, packet) + : pickHeadline(verdict, packet); + const narrative = reverted + ? "The transaction reverted, so no state was modified. A reverted call cannot be a successful exploit — the LLM verdict is shown below for context only." + : pickNarrative(verdict); + const confidencePct = reverted ? 0 : Math.round(verdict.confidence * 100); + const displayHash = txHash ?? packet.txHash ?? null; + + const FilterPill: React.FC<{ id: ActivityFilter; label: string; n: number }> = ({ id, label, n }) => { + const active = filter === id; + return ( + + ); + }; + + return ( + +
+
+
+
+ + HEXKIT + · summary + {chainName ? ( + {chainName} + ) : null} +
+ +
+ Transaction type +
{typeLabel.type}
+
{typeLabel.category}
+
+ +
+ Status +
+ + {status.label} + +
+
+ +
+ Confidence +
+
+
+
+ {confidencePct}% +
+
+ +
+ + +
+
+ +
+
+ {headline} +
+ {narrative ? ( +
{narrative}
+ ) : null} + + {error ? ( +
+ {error.title} + {error.detail ? {error.detail} : null} +
+ ) : null} + +
+
+ Activity · {total} +
+ + + + + +
+
+ +
+
+ {visible.map((a, i) => { + const ts = activityTypeStyle(a.kind, a.dir); + return ( +
+ + {ts.icon} + {ts.label} + +
+
+ {a.label} +
+ {a.sub ? ( +
+ {a.sub} +
+ ) : null} +
+ {a.value ? ( +
+
{a.value}
+ {a.valueSub ? ( +
{a.valueSub}
+ ) : null} +
+ ) : ( + + )} +
+ ); + })} + {visible.length === 0 && ( +
+ No matching activity in this filter. +
+ )} +
+
+
+
+ +
+ Hash + + {displayHash ?? "—"} + + {displayHash ? ( + + ) : null} +
+
+
+
+ + ); +}; + +export default SummaryCard; diff --git a/src/components/tx-analysis/TxAnalysisPanel.tsx b/src/components/tx-analysis/TxAnalysisPanel.tsx new file mode 100644 index 0000000..4a0c033 --- /dev/null +++ b/src/components/tx-analysis/TxAnalysisPanel.tsx @@ -0,0 +1,680 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTxAnalysis, type AnalysisStatus } from "./useTxAnalysis"; +import { useHackAnalysis } from "./useHackAnalysis"; +import { useHackTriage } from "./useHackTriage"; +import { HackAnalysisPanel } from "./HackAnalysisPanel"; +import { HackTriagePanel } from "./HackTriagePanel"; +import { DeepDiveDrawer } from "./DeepDiveDrawer"; +import { SummaryCard } from "./SummaryCard"; +import { verdictToMarkdown } from "../../utils/tx-analysis/markdown"; +import { LlmError } from "../../utils/llm/types"; +import type { BridgeSimulationResponsePayload } from "../../utils/transaction-simulation/types"; +import type { + HeimdallSourceBundle, + VerifiedSourceBundle, +} from "../../utils/tx-analysis/deepDive"; +import { contractResolver } from "../../utils/resolver/ContractResolver"; +import { getChainById } from "../../chains/registry"; +import { fetchHeimdallDecompilation } from "../../utils/heimdall/heimdallApi"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { cn } from "@/lib/utils"; +import NetworkSelector, { + EXTENDED_NETWORKS, + type ExtendedChain, +} from "../shared/NetworkSelector"; +import { + defaultReplayNetwork, + mapExtendedToChain, + replayCardStyle, + replayGridContainerStyle, + replayGridStyle, + replaySectionStyle, + replaySectionTitleStyle, +} from "../transaction-builder/types"; +import { replayTransactionWithSimulator } from "../../utils/transactionSimulation"; +import { useSimulation } from "../../contexts/SimulationContext"; +import { classifySimulationError } from "../../utils/errorParser"; +import { useTxPreview } from "../../hooks/useTxPreview"; +import { + CircleNotch, + Detective, + DownloadSimple, + StopCircle, + MagnifyingGlass, + Play, +} from "@phosphor-icons/react"; + +const HACK_TRIAGE_ADDRESS: `0x${string}` = + (import.meta.env.VITE_HACK_TRIAGE_ADDRESS as `0x${string}` | undefined) ?? + "0xBe02be322c7733759ee068067BD620791e9e73D4"; + +const TX_HASH_RE = /^0x[a-fA-F0-9]{64}$/; + +function formatAnalysisError(err: Error): { title: string; detail?: string } { + if (err.name === "AbortError") return { title: "Analysis cancelled." }; + if (err instanceof LlmError) { + switch (err.errorClass) { + case "context_overflow": + return { + title: "Evidence too large for the proxy.", + detail: "Try cancelling verified-source fetching, or run a simpler analysis first.", + }; + case "rate_limit": + return { + title: "LLM rate-limited.", + detail: "Wait a moment and retry.", + }; + case "bad_key": + return { + title: "LLM key rejected.", + detail: "Check provider credentials in Settings.", + }; + case "provider_down": + return { + title: "LLM provider unavailable.", + detail: "Try again in a moment or switch providers.", + }; + case "network": + return { + title: "Network error talking to the LLM proxy.", + detail: "Check the proxy process is running.", + }; + case "schema_invalid": + return { + title: "LLM returned a malformed verdict.", + detail: "Try re-running; if it persists, lower the temperature.", + }; + default: + return { title: "LLM call failed.", detail: err.message }; + } + } + return { title: "Analysis failed.", detail: err.message }; +} + +const STATUS_LABEL: Record, string> = { + extracting: "Extracting evidence from simulation…", + llm: "Asking the model…", + deep_dive: "Reading verified + decompiled source…", +}; + +interface Props { + simulation: BridgeSimulationResponsePayload | null; + simulationId: string | null; + from: string | null; + to: string | null; + txHash: string | null; +} + +function download(filename: string, content: string, mime = "text/plain") { + const blob = new Blob([content], { type: mime }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); +} + +export const TxAnalysisPanel: React.FC = ({ simulation, simulationId, from, to, txHash }) => { + // `to` is null for contract-creation txs (e.g., Beanstalk exploit where the + // attacker deployed+invoked the exploit in the same tx). Don't gate on it. + const ready = Boolean(simulation && simulationId && from); + const { setAnalysisSubject } = useSimulation(); + + const [formTxHash, setFormTxHash] = useState(""); + const [formNetwork, setFormNetwork] = useState(defaultReplayNetwork); + const [formError, setFormError] = useState(null); + const [isFetching, setIsFetching] = useState(false); + const [autoRunPending, setAutoRunPending] = useState(false); + + const { + txPreview: previewTx, + txFetchStatus: previewStatus, + txFetchError: previewError, + } = useTxPreview({ txHash: formTxHash, selectedNetwork: formNetwork }); + + const deepDiveFetchers = useMemo(() => { + const chainId = simulation?.chainId ?? null; + if (!chainId) return undefined; + const chain = getChainById(chainId); + if (!chain) return undefined; + + const fetchVerifiedSource = async (address: string): Promise => { + const result = await contractResolver.resolve(address, chain); + const sources = result.metadata?.sources; + if (!result.verified || !sources || Object.keys(sources).length === 0) return null; + const provider: VerifiedSourceBundle["provider"] = + result.source === "etherscan" || result.source === "sourcify" || result.source === "blockscout" + ? result.source + : "sourcify"; + return { + contractName: result.name ?? address, + files: Object.entries(sources).map(([path, source]) => ({ path, source })), + provider, + }; + }; + + const fetchHeimdallDecompile = async (address: string): Promise => { + try { + const res = await fetchHeimdallDecompilation({ address, chainId }); + return { source: res.source, provider: "heimdall" }; + } catch { + return null; + } + }; + + return { fetchVerifiedSource, fetchHeimdallDecompile }; + }, [simulation?.chainId]); + + const analysis = useTxAnalysis({ + simulation: simulation ?? null, + simulationId: simulationId ?? "", + from: from ?? "", + to: to || null, + txHash, + deepDiveFetchers, + }); + const hack = useHackAnalysis({ packet: analysis.packet, invoke: analysis.invokeFn }); + const triage = useHackTriage({ packet: analysis.packet, contractAddress: HACK_TRIAGE_ADDRESS }); + const [deepOpen, setDeepOpen] = useState(false); + const [deeperScanOpen, setDeeperScanOpen] = useState(false); + + useEffect(() => { + setDeepOpen(false); + setDeeperScanOpen(false); + }, [simulationId, txHash]); + + const triageBusy = + triage.status === "encrypting" || + triage.status === "writing" || + triage.status === "waiting-fhe" || + triage.status === "decrypting"; + const hackBusy = + hack.status === "classifying" || + hack.status === "retrieving" || + hack.status === "llm"; + + const handleFetchAndAnalyze = useCallback(async () => { + const trimmed = formTxHash.trim(); + if (!TX_HASH_RE.test(trimmed)) { + setFormError("Enter a valid 32-byte transaction hash (0x-prefixed)."); + return; + } + if (previewStatus !== "found") { + setFormError( + previewError || + (previewStatus === "fetching" + ? "Still validating the transaction on the selected network…" + : `Transaction not found on ${formNetwork.name}. Confirm the hash and network before analyzing.`), + ); + return; + } + setFormError(null); + setIsFetching(true); + try { + const chainForReplay = mapExtendedToChain(formNetwork); + const sim = await replayTransactionWithSimulator(chainForReplay, trimmed); + if (!sim) { + setFormError( + "Simulator bridge unavailable. Run `npm run simulator:server` and ensure the edb-simulator binary is built.", + ); + return; + } + if (sim.success === false && sim.error) { + const classified = classifySimulationError(sim.error); + setFormError( + classified.suggestion ? `${classified.message} ${classified.suggestion}` : classified.message, + ); + return; + } + const generatedSimulationId = crypto.randomUUID(); + const enriched = { + ...sim, + networkName: formNetwork.name, + chainId: formNetwork.id, + transactionHash: trimmed, + simulationId: generatedSimulationId, + }; + // eslint-disable-next-line no-console + console.log("[tx-analysis] sim result", { from: sim.from, to: sim.to, hasResponse: !!sim, keys: Object.keys(sim || {}) }); + setAnalysisSubject({ + simulationId: generatedSimulationId, + from: sim.from ?? "", + to: sim.to ?? null, + txHash: trimmed, + simulation: enriched as unknown as BridgeSimulationResponsePayload, + }); + setAutoRunPending(true); + } catch (err: any) { + const classified = classifySimulationError(err?.message || "Failed to fetch transaction"); + setFormError(classified.message); + } finally { + setIsFetching(false); + } + }, [formTxHash, formNetwork, setAnalysisSubject, previewStatus, previewError]); + + useEffect(() => { + // eslint-disable-next-line no-console + console.log("[tx-analysis] auto-run effect", { + autoRunPending, + ready, + hasSimulation: !!simulation, + hasSimId: !!simulationId, + from, + to, + status: analysis.status, + }); + if (!autoRunPending || !ready) return; + if (analysis.status !== "idle" && analysis.status !== "ready") return; + setAutoRunPending(false); + // eslint-disable-next-line no-console + console.log("[tx-analysis] calling runSimple"); + analysis.runSimple().catch((e) => { + // eslint-disable-next-line no-console + console.error("[tx-analysis] runSimple failed:", e?.message, e?.stack); + }); + }, [autoRunPending, ready, analysis, simulation, simulationId, from, to]); + + const hashValid = formTxHash.trim() !== "" && TX_HASH_RE.test(formTxHash.trim()); + const runDisabled = !formTxHash.trim() || isFetching || previewStatus !== "found"; + + const busy = + analysis.status === "extracting" || + analysis.status === "llm" || + analysis.status === "deep_dive"; + const statusLabel = + analysis.status === "extracting" || analysis.status === "llm" || analysis.status === "deep_dive" + ? STATUS_LABEL[analysis.status] + : null; + + const analysisGridStyle: React.CSSProperties = { + ...replayGridStyle, + maxWidth: ready ? "900px" : "600px", + }; + + return ( +
+
+
+

Analyze a Transaction

+
+
+ setFormTxHash(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter" && !runDisabled) { + event.preventDefault(); + handleFetchAndAnalyze(); + } + }} + placeholder="0x0000…0000" + disabled={isFetching} + className={cn( + "h-12 pl-4 pr-[120px] font-mono text-sm tracking-tight transition-all duration-300", + "bg-transparent! border-slate-800/50 hover:border-slate-700/60 focus:ring-0 focus:border-white/50", + hashValid && "border-white/30 bg-white/[0.02]", + )} + /> +
+ + +
+
+ {!ready ? ( +

+ Paste a transaction hash and we'll replay it, then summarize what it did. Or click{" "} + Summarize on an existing simulation result. +

+ ) : null} + {isFetching ? ( +
+ + Replaying transaction on {formNetwork.name}… +
+ ) : null} + {!isFetching && hashValid && previewStatus === "fetching" ? ( +
+ + Validating transaction on {formNetwork.name}… +
+ ) : null} + {!isFetching && hashValid && previewStatus === "found" && previewTx ? ( +

+ Found in block {previewTx.blockNumber ?? "—"} · from {previewTx.from.slice(0, 6)}…{previewTx.from.slice(-4)} + {previewTx.to ? ` · to ${previewTx.to.slice(0, 6)}…${previewTx.to.slice(-4)}` : ""} +

+ ) : null} + {!isFetching && hashValid && (previewStatus === "not_found" || previewStatus === "error") && previewError ? ( +

+ {previewError} +

+ ) : null} + {formError ? ( +

+ {formError} +

+ ) : null} +
+
+ + {!ready ? null : ( +
+
+ + {busy ? ( + + ) : null} + {analysis.verdict && analysis.verdict.deepDive ? ( + + ) : null} + {analysis.verdict ? ( + <> + + + + ) : null} +
+ + {statusLabel ? ( +
+ + {statusLabel} +
+ ) : null} + + {analysis.verdict && analysis.packet ? ( + setDeeperScanOpen(true)} + onCopyHash={txHash ? () => void navigator.clipboard?.writeText(txHash) : undefined} + error={analysis.error ? formatAnalysisError(analysis.error) : null} + /> + ) : analysis.error ? ( + (() => { + const friendly = formatAnalysisError(analysis.error); + return ( +

+ {friendly.title} + {friendly.detail ? {friendly.detail} : null} +

+ ); + })() + ) : null} + + {/* Deeper-scan section: cleartext and private analysts side by side, gated + behind explicit user opt-in (the SummaryCard's Deep Scan button) so the + public LLM summary stays the always-on first read. */} + {analysis.verdict && analysis.packet && deeperScanOpen ? ( +
+
+
+

+ Deeper scan +

+

+ Run cross-references against known hacks, or send the encrypted feature vector to Sepolia for an FHE verdict that downstream contracts can consume. +

+
+ +
+ +
+ {/* Cleartext analog matcher */} +
+
+ + Cleartext · LLM + +
+

+ Matches the evidence packet against a corpus of known exploit patterns. Inputs leave your browser unencrypted. +

+
+ + {hackBusy ? ( + + ) : null} +
+ {hack.analysis ? ( + + ) : null} +
+ + {/* Encrypted (FHE) classification on Sepolia */} +
+
+ + 🔒 Encrypted · Sepolia FHE + +
+

+ Encrypts a 12-bit feature vector in-browser, classifies on Sepolia under FHE, decrypts the verdict locally under your permit. The chain never sees the inputs. +

+
+ + {triageBusy ? ( + + ) : null} +
+ {triageBusy ? ( +
+ + + {triage.status === "encrypting" && "Encrypting…"} + {triage.status === "writing" && "Writing to Sepolia…"} + {triage.status === "waiting-fhe" && "Waiting for CoFHE coprocessor… (~30s)"} + {triage.status === "decrypting" && "Decrypting verdict…"} + +
+ ) : null} + {triage.status === "error" && triage.error ? ( +

+ {triage.error} +

+ ) : null} + {triage.status === "ready" && triage.verdict ? ( + + ) : null} +
+
+
+ ) : null} + + {analysis.verdict ? ( + setDeepOpen(false)} + verdict={analysis.verdict} + /> + ) : null} +
+ )} +
+
+ ); +}; + +export default TxAnalysisPanel; diff --git a/src/components/tx-analysis/TxSummarySamplesPage.tsx b/src/components/tx-analysis/TxSummarySamplesPage.tsx new file mode 100644 index 0000000..c45523b --- /dev/null +++ b/src/components/tx-analysis/TxSummarySamplesPage.tsx @@ -0,0 +1,894 @@ +import React from "react"; + +// HexKit design-system samples page. +// Tokens mirror /tmp/anthropic-design-v2/hexkit-design-system/project/colors_and_type.css. +// All styles are scoped under `.hk-root.dark` so they don't leak into the rest of the app. + +const KIT_CSS = ` +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:ital,wght@0,400;0,500;0,700;1,400&display=swap'); + +.hk-root.dark { + --bg-primary: #0a0a0a; + --bg-secondary: #171717; + --bg-tertiary: #262626; + --bg-elevated: #1c1c1c; + --bg-glass: rgba(23,23,23,0.9); + --bg-card: rgba(38,38,38,0.95); + + --text-primary: #fafafa; + --text-secondary: #a1a1aa; + --text-tertiary: #71717a; + --text-muted: #52525b; + --text-accent: #d4d4d4; + + --border-primary: rgba(255,255,255,0.10); + --border-secondary: rgba(255,255,255,0.05); + --border-accent: rgba(255,255,255,0.30); + --border-focus: rgba(255,255,255,0.50); + + --accent-primary-10: rgba(255,255,255,0.10); + --accent-primary-20: rgba(255,255,255,0.20); + --accent-primary-30: rgba(255,255,255,0.30); + + --success: #22c55e; + --warning: #f59e0b; + --error: #ef4444; + + --font-body: 'Inter', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Consolas, monospace; + + background: var(--bg-primary); + color: var(--text-primary); + font-family: var(--font-body); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-feature-settings: 'cv11','ss01'; +} + +.hk-root * { box-sizing: border-box; } + +.hk-root .mono { font-family: var(--font-mono); } +.hk-root .brand-wordmark { + font-family: var(--font-mono); + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} +.hk-root .label-caps { + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 11px; + color: var(--text-tertiary); + font-weight: 500; +} + +.hk-root .constellation { + position: absolute; inset: 0; pointer-events: none; z-index: 0; + background-image: + radial-gradient(circle at 1px 1px, rgba(255,255,255,0.055) 1px, transparent 0), + radial-gradient(circle at 18px 18px, rgba(255,255,255,0.03) 1px, transparent 0); + background-size: 36px 36px, 72px 72px; + -webkit-mask-image: radial-gradient(ellipse at 50% 0%, black 55%, transparent 95%); + mask-image: radial-gradient(ellipse at 50% 0%, black 55%, transparent 95%); +} + +.hk-root .topbar { + position: sticky; top: 0; z-index: 10; + background: var(--bg-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-bottom: 1px solid var(--border-primary); +} + +.hk-root .hk-card { + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 12px; + box-shadow: 0 1px 2px rgba(0,0,0,0.3); +} +.hk-root .hk-card-elevated { + background: var(--bg-elevated); + border: 1px solid var(--border-primary); + border-radius: 12px; + box-shadow: 0 10px 15px rgba(0,0,0,0.3); +} +.hk-root .hk-card-accent { + background: rgba(255,255,255,0.05); + border: 1px solid var(--accent-primary-20); + border-radius: 12px; +} +.hk-root .hk-card-glass { + background: var(--bg-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--border-primary); + border-radius: 12px; +} + +.hk-root .hk-btn { + display: inline-flex; align-items: center; gap: 8px; + font-family: var(--font-body); font-weight: 500; font-size: 13px; + border-radius: 8px; padding: 9px 14px; + border: 1px solid transparent; cursor: pointer; + transition: all 150ms ease; white-space: nowrap; line-height: 1; +} +.hk-root .hk-btn-primary { background: #fafafa; color: #0a0a0a; } +.hk-root .hk-btn-primary:hover { background: rgba(250,250,250,0.9); } +.hk-root .hk-btn-secondary { background: #262626; color: #fafafa; border-color: rgba(255,255,255,0.10); } +.hk-root .hk-btn-secondary:hover { background: #2e2e2e; } +.hk-root .hk-btn-outline { background: transparent; color: #fafafa; border-color: rgba(255,255,255,0.20); } +.hk-root .hk-btn-outline:hover { background: rgba(255,255,255,0.05); } +.hk-root .hk-btn-ghost { background: transparent; color: #a1a1aa; } +.hk-root .hk-btn-ghost:hover { background: #262626; color: #fafafa; } +.hk-root .hk-btn:active { transform: scale(0.96); } + +.hk-root .hk-pill { + display: inline-flex; align-items: center; gap: 6px; + padding: 3px 10px; border-radius: 9999px; + font-size: 11px; font-weight: 500; + border: 1px solid; line-height: 1; +} +.hk-root .pill-default { background: #262626; color: #a1a1aa; border-color: rgba(255,255,255,0.10); } +.hk-root .pill-success { background: rgba(34,197,94,0.18); color: #bbf7d0; border-color: rgba(34,197,94,0.30); } +.hk-root .pill-warning { background: rgba(245,158,11,0.15); color: #facc15; border-color: rgba(245,158,11,0.35); } +.hk-root .pill-error { background: rgba(239,68,68,0.18); color: #fecaca; border-color: rgba(239,68,68,0.35); } +.hk-root .pill-accent { background: rgba(255,255,255,0.08); color: #ffffff; border-color: rgba(255,255,255,0.20); } +.hk-root .pill-mono { + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.hk-root .dot { width: 6px; height: 6px; border-radius: 9999px; background: currentColor; } +.hk-root .dot-glow { box-shadow: 0 0 8px currentColor; } + +.hk-root .hk-kbd { + display: inline-flex; align-items: center; justify-content: center; + min-width: 22px; height: 22px; padding: 0 6px; + border: 1px solid rgba(255,255,255,0.15); background: #262626; color: #a1a1aa; + border-radius: 4px; font-family: var(--font-mono); font-size: 11px; +} + +.hk-root .hk-input { + width: 100%; + background: #171717; + border: 1px solid rgba(255,255,255,0.10); + border-radius: 12px; + padding: 11px 14px; + color: #fafafa; + font-family: var(--font-body); + font-size: 13px; outline: none; + transition: all 150ms ease; +} +.hk-root .hk-input:focus { border-color: rgba(255,255,255,0.50); box-shadow: 0 0 0 2px rgba(255,255,255,0.20); } + +.hk-root .divider-h { height: 1px; background: var(--border-primary); width: 100%; } +.hk-root .divider-dash { border-top: 1px dashed var(--border-primary); width: 100%; } +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Mock sample payload — a Yearn yvWETH deposit on Ethereum mainnet. +// ───────────────────────────────────────────────────────────────────────────── + +const SAMPLE = { + txHash: "0x329528b97cdb193f3f1c65689b8341ef33db4c04958172a6665f1381bfd1378f", + chainId: 1, + chainName: "Ethereum", + block: 23127448, + ts: "2026-04-17 14:12:07 UTC", + status: "SUCCESS" as const, + from: { addr: "0x1F26…9c42", label: "User (EOA)" }, + to: { addr: "0x5f18…c3A1", label: "yvWETH · Yearn V3 Vault" }, + txType: "Vault deposit" as const, + txCategory: "DeFi · Yield" as const, + narrative: + "User wrapped 2 ETH into WETH, approved the Yearn v3 yvWETH vault, then deposited the WETH and received 1.9712 yvWETH shares representing their position in the strategy.", + transferred: [ + { dir: "OUT", token: "WETH", amount: "2.0000", usd: "$6,284.10", from: "User", to: "yvWETH" }, + { dir: "IN", token: "yvWETH", amount: "1.9712", usd: "$6,284.10", from: "yvWETH", to: "User" }, + ], + gas: { used: "187,432", priceGwei: "24.7", totalEth: "0.00463", totalUsd: "$14.57" }, + contracts: [ + { name: "WETH9", addr: "0xC02a…6Cc2", verified: true }, + { name: "yvWETH", addr: "0x5f18…c3A1", verified: true }, + ], + selectors: [ + { sig: "deposit(uint256,address)", sel: "0x6e553f65" }, + { sig: "transfer(address,uint256)", sel: "0xa9059cbb" }, + { sig: "approve(address,uint256)", sel: "0x095ea7b3" }, + ], + confidence: 0.96, + steps: [ + { i: 1, label: "WETH.deposit{value: 2 ETH}()", detail: "User wraps 2 ETH → WETH9" }, + { i: 2, label: "WETH.approve(yvWETH, 2e18)", detail: "Grants vault spending allowance" }, + { i: 3, label: "yvWETH.deposit(2e18, user)", detail: "Transfers WETH in, mints shares" }, + { i: 4, label: "yvWETH.transfer(user, 1.9712e18)", detail: "Shares delivered to user" }, + ], + // A fuller activity trace — mix of transfers, calls, events, approvals — + // used by Sample 10's right-rail feed to prove it scales past 2 rows. + activities: [ + { kind: "eth", dir: "out", label: "ETH wrapped into WETH", sub: "User → WETH9", value: "2.0000 ETH", valueSub: "$6,284.10" }, + { kind: "call", label: "WETH.deposit{value:2e18}()", sub: "wrap native · WETH9", value: "0xd0e30db0" }, + { kind: "event", label: "Deposit(dst, wad)", sub: "WETH9 · topic0 0xe1ff…", value: "log #14" }, + { kind: "approve", label: "Allowance set → yvWETH", sub: "User · WETH9.approve", value: "2.0000 WETH" }, + { kind: "call", label: "yvWETH.deposit(2e18, user)", sub: "Yearn V3 vault entrypoint", value: "0x6e553f65" }, + { kind: "call", label: "ERC20.transferFrom pull", sub: "yvWETH ← User (WETH9)", value: "0x23b872dd" }, + { kind: "transfer", dir: "out", label: "WETH sent to yvWETH", sub: "User → yvWETH", value: "2.0000 WETH", valueSub: "$6,284.10" }, + { kind: "event", label: "Transfer(User, yvWETH, 2e18)", sub: "WETH9 · topic0 0xddf2…", value: "log #27" }, + { kind: "transfer", dir: "in", label: "yvWETH shares minted", sub: "yvWETH → User", value: "1.9712 yvWETH", valueSub: "$6,284.10" }, + { kind: "event", label: "Deposit(sender, owner, assets, shares)", sub: "yvWETH · topic0 0xdcbc…", value: "log #31" }, + { kind: "call", label: "Strategy harvest hook", sub: "yvWETH · delegatecall", value: "0x4641257d" }, + { kind: "transfer", dir: "in", label: "Reward dust", sub: "yvWETH → User", value: "0.0001 CRV", valueSub: "$0.12" }, + { kind: "approve", label: "Allowance cleared", sub: "User · WETH9.approve", value: "0" }, + { kind: "event", label: "Harvested(strategy, profit)", sub: "yvWETH · topic0 0x4c09…", value: "log #34" }, + ] as ReadonlyArray<{ + kind: "transfer" | "eth" | "call" | "event" | "approve"; + dir?: "in" | "out"; + label: string; + sub?: string; + value?: string; + valueSub?: string; + }>, +}; + +// Small inline icons (phosphor-style 2px stroke). +const I = { + arrowUp: (p: { size?: number } = {}) => ( + + ), + arrowDown: (p: { size?: number } = {}) => ( + + ), + arrowRight: (p: { size?: number } = {}) => ( + + ), + copy: (p: { size?: number } = {}) => ( + + ), + hex: (p: { size?: number } = {}) => ( + + ), + spark: (p: { size?: number } = {}) => ( + + ), + flow: (p: { size?: number } = {}) => ( + + ), + check: (p: { size?: number } = {}) => ( + + ), + clock: (p: { size?: number } = {}) => ( + + ), +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Shared fragments +// ───────────────────────────────────────────────────────────────────────────── + +const TxTypeBadge: React.FC = () => ( + + {SAMPLE.txType} + +); + +const StatusBadge: React.FC = () => ( + + {SAMPLE.status} + +); + +const ConfidenceChip: React.FC = () => ( + + CONF {(SAMPLE.confidence * 100).toFixed(0)}% + +); + +const AddrMono: React.FC<{ addr: string; label?: string }> = ({ addr, label }) => ( + + {label && {label}} + {addr} + + +); + +const MicroLabel: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
{children}
+); + +// ───────────────────────────────────────────────────────────────────────────── +// 10 sample layouts +// ───────────────────────────────────────────────────────────────────────────── + +type SampleShell = React.FC<{ id: string; title: string; children: React.ReactNode }>; + +const Shell: SampleShell = ({ id, title, children }) => ( +
+
+
+ {id} + {title} +
+ +
+ {children} +
+); + +// ── Sample 01 · Verdict Header ───────────────────────────────────────────── +const Sample01: React.FC = () => ( +
+
+ + + +
+ {SAMPLE.ts} · block {SAMPLE.block.toLocaleString()} +
+
+
+ Deposited 2.0000 WETH into yvWETH +
+
+ {SAMPLE.narrative} +
+
+
+
From
{SAMPLE.from.label}
{SAMPLE.from.addr}
+
To
{SAMPLE.to.label}
{SAMPLE.to.addr}
+
Network
{SAMPLE.chainName}
chainId {SAMPLE.chainId}
+
Gas
{SAMPLE.gas.totalEth} ETH
{SAMPLE.gas.totalUsd} · {SAMPLE.gas.priceGwei} gwei
+
+
+); + +// ── Sample 02 · Transfer Ledger ───────────────────────────────────────────── +const Sample02: React.FC = () => ( +
+
+ +
+ tx{" "} + {SAMPLE.txHash.slice(0, 10)}…{SAMPLE.txHash.slice(-6)} +
+
+ +
+
+
Dir
+
Asset
+
Movement
+
Value
+
+ {SAMPLE.transferred.map((t, i) => { + const isOut = t.dir === "OUT"; + return ( +
+
+ + {isOut ? : } {t.dir} + +
+
+
{t.token}
+
ERC-20
+
+
+ {t.from} + + {t.to} +
+
+
{t.amount}
+
{t.usd}
+
+
+ ); + })} +
+
+); + +// ── Sample 03 · Flow diagram ───────────────────────────────────────────────── +const Node: React.FC<{ label: string; sub: string; accent?: boolean }> = ({ label, sub, accent }) => ( +
+
{accent ? "Vault" : "Actor"}
+
{label}
+
{sub}
+
+); +const Edge: React.FC<{ label: string; sub: string; tone?: "out" | "in" }> = ({ label, sub, tone }) => ( +
+
{label}
+
+ + +
+
{sub}
+
+); +const Sample03: React.FC = () => ( +
+
+ Flow + + + +
+
+ + + + + +
+
+
+ {SAMPLE.narrative} +
+
+); + +// ── Sample 04 · Terminal block ───────────────────────────────────────────── +const Sample04: React.FC = () => ( +
+
+ + + + + hexkit ▸ tx-analyze {SAMPLE.txHash.slice(0, 14)}… + +
+ + +
+
+
+
▸ summary.verdict{" "}{SAMPLE.txType}
+
▸ summary.category {SAMPLE.txCategory}
+
▸ summary.from {SAMPLE.from.addr} // {SAMPLE.from.label}
+
▸ summary.to {SAMPLE.to.addr} // {SAMPLE.to.label}
+
▸ transfers[]
+ {SAMPLE.transferred.map((t, i) => ( +
+ {t.dir.padEnd(4)}{" "} + {t.amount} {t.token}{" "} + ({t.usd}){" "} + {t.from} → {t.to} +
+ ))} +
▸ narrative
+
+ {SAMPLE.narrative} +
+
+ {" "} + verdict emitted · confidence{" "} + {(SAMPLE.confidence * 100).toFixed(0)}% +
+
+
+); + +// ── Sample 05 · Split header + evidence ───────────────────────────────────── +const Sample05: React.FC = () => ( +
+
+
+
+ + +
+
+ {SAMPLE.narrative.split(",")[0]}. +
+
+ {SAMPLE.narrative.split(",").slice(1).join(",").trim()} +
+
+
+ {SAMPLE.transferred.map((t, i) => ( +
+
{t.dir}
+
{t.amount}
+
{t.token} · {t.usd}
+
+ ))} +
+
+
+
Evidence
+
+ {SAMPLE.selectors.map((s, i) => ( +
+
{s.sig}
+ {s.sel} +
+ ))} +
+
+
Contracts
+ {SAMPLE.contracts.map((c, i) => ( +
+
+
{c.name}
+
{c.addr}
+
+ Verified +
+ ))} +
+
+
+); + +// ── Sample 06 · Metric grid ───────────────────────────────────────────────── +const Stat: React.FC<{ label: string; value: string; sub?: string; mono?: boolean }> = ({ label, value, sub, mono }) => ( +
+
{label}
+
{value}
+ {sub &&
{sub}
} +
+); +const Sample06: React.FC = () => ( +
+
+ + + +
+ hash{" "} + {SAMPLE.txHash.slice(0, 14)}…{SAMPLE.txHash.slice(-8)} +
+
+
+ + + + + + +
+
+ Narrative +
{SAMPLE.narrative}
+
+
+); + +// ── Sample 07 · Timeline ──────────────────────────────────────────────────── +const Sample07: React.FC = () => ( +
+
+ +
+ 4 inner steps +
+
+
+ {SAMPLE.steps.map((s, i) => ( +
+
+
{s.i}
+ {i !== SAMPLE.steps.length - 1 &&
} +
+
+
{s.label}
+
{s.detail}
+
+
+ ok +
+
+ ))} +
+
+); + +// ── Sample 08 · Narrative-first ──────────────────────────────────────────── +const Sample08: React.FC = () => ( +
+
+ TX · SUMMARY + #{SAMPLE.block.toLocaleString()} +
+ +
+
+
+ This is a {SAMPLE.txType.toLowerCase()}.{" "} + {SAMPLE.from.label}{" "} + deposited{" "} + 2.0000 WETH{" "} + into{" "} + yvWETH{" "} + and received{" "} + 1.9712 yvWETH{" "} + shares. +
+
+
+ 2.0000 WETH out + 1.9712 yvWETH in + gas {SAMPLE.gas.totalEth} ETH + {SAMPLE.gas.priceGwei} gwei + {SAMPLE.chainName} + {SAMPLE.txHash.slice(0, 10)}…{SAMPLE.txHash.slice(-6)} +
+
+); + +// ── Sample 09 · Field grid (dense) ───────────────────────────────────────── +const Field: React.FC<{ k: string; v: React.ReactNode; mono?: boolean }> = ({ k, v, mono }) => ( +
+
{k}
+
{v}
+
+); +const Sample09: React.FC = () => ( +
+
+ +
Dense field grid
+
+ + {SAMPLE.chainName} (id {SAMPLE.chainId})} /> + {SAMPLE.block.toLocaleString()} · {SAMPLE.ts}} /> + {SAMPLE.from.label} {SAMPLE.from.addr}} /> + {SAMPLE.to.label} {SAMPLE.to.addr}} /> + - 2.0000 WETH ($6,284.10)} /> + + 1.9712 yvWETH ($6,284.10)} /> + {SAMPLE.gas.used} units · {SAMPLE.gas.priceGwei} gwei → {SAMPLE.gas.totalEth} ETH ({SAMPLE.gas.totalUsd})} /> + + {SAMPLE.selectors.map((s, i) => ( + {s.sel} + ))} + + } /> + + + + + {(SAMPLE.confidence * 100).toFixed(0)}% + } /> +
+); + +// ── Sample 10 · Left rail verdict + right activity feed ────────────────── +type ActivityKind = "transfer" | "eth" | "call" | "event" | "approve"; +type ActivityFilter = "all" | "transfer" | "call" | "event" | "approve"; + +const activityTypeStyle = (kind: ActivityKind, dir?: "in" | "out") => { + if (kind === "transfer" || kind === "eth") { + const out = dir === "out"; + return { + bg: out ? "rgba(245,158,11,0.14)" : "rgba(34,197,94,0.14)", + border: out ? "rgba(245,158,11,0.30)" : "rgba(34,197,94,0.30)", + color: out ? "#facc15" : "#bbf7d0", + label: kind === "eth" ? "ETH" : "TRANSFER", + icon: out ? : , + }; + } + if (kind === "approve") { + return { bg: "rgba(255,255,255,0.06)", border: "rgba(255,255,255,0.18)", color: "#e5e5e5", label: "APPROVE", icon: null }; + } + if (kind === "call") { + return { bg: "rgba(255,255,255,0.10)", border: "rgba(255,255,255,0.25)", color: "#ffffff", label: "CALL", icon: null }; + } + // event + return { bg: "#262626", border: "rgba(255,255,255,0.10)", color: "#a1a1aa", label: "EVENT", icon: null }; +}; + +const Sample10: React.FC = () => { + const [filter, setFilter] = React.useState("all"); + const total = SAMPLE.activities.length; + const counts = { + transfer: SAMPLE.activities.filter((a) => a.kind === "transfer" || a.kind === "eth").length, + call: SAMPLE.activities.filter((a) => a.kind === "call").length, + event: SAMPLE.activities.filter((a) => a.kind === "event").length, + approve: SAMPLE.activities.filter((a) => a.kind === "approve").length, + }; + const visible = SAMPLE.activities.filter((a) => { + if (filter === "all") return true; + if (filter === "transfer") return a.kind === "transfer" || a.kind === "eth"; + return a.kind === filter; + }); + + const FilterPill: React.FC<{ id: ActivityFilter; label: string; n: number }> = ({ id, label, n }) => { + const active = filter === id; + return ( + + ); + }; + + return ( +
+
+
+
+ + HEXKIT + · summary +
+
+ Transaction type +
{SAMPLE.txType}
+
{SAMPLE.txCategory}
+
+
+ Status +
+
+
+ Confidence +
+
+
+
+ {(SAMPLE.confidence * 100).toFixed(0)}% +
+
+
+ + +
+
+ +
+
+ {SAMPLE.from.label} deposited 2.0000 WETH into {SAMPLE.to.label} +
+
{SAMPLE.narrative}
+ +
+
+ Activity · {total} +
+ + + + + +
+
+ +
+
+ {visible.map((a, i) => { + const ts = activityTypeStyle(a.kind as ActivityKind, a.dir); + return ( +
+ + {ts.icon}{ts.label} + +
+
{a.label}
+ {a.sub &&
{a.sub}
} +
+ {a.value ? ( +
+
{a.value}
+ {a.valueSub &&
{a.valueSub}
} +
+ ) : } +
+ ); + })} + {visible.length === 0 && ( +
+ No matching activity in this filter. +
+ )} +
+
+
+
+ +
+ Hash + {SAMPLE.txHash} + +
+
+
+
+ ); +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Page root +// ───────────────────────────────────────────────────────────────────────────── + +const TxSummarySamplesPage: React.FC = () => { + return ( +
+ +
+ +
+
+ + HEXKIT + / tx-summary-samples +
+
+ {SAMPLE.chainName.toUpperCase()} + {SAMPLE.txHash.slice(0, 10)}…{SAMPLE.txHash.slice(-6)} + ESC +
+
+ +
+
+ Design study · pick one +

+ Ten ways to render a tx summary verdict +

+

+ Each card renders the same evidence — a Yearn v3 yvWETH deposit on Ethereum — in a different visual + register. Pick the one that should become the default layout for TxAnalysisPanel's + summary mode. Deep scan mode will layer on top. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +}; + +export default TxSummarySamplesPage; diff --git a/src/components/tx-analysis/VerdictCard.tsx b/src/components/tx-analysis/VerdictCard.tsx new file mode 100644 index 0000000..47a95db --- /dev/null +++ b/src/components/tx-analysis/VerdictCard.tsx @@ -0,0 +1,76 @@ +import React from "react"; +import type { Verdict } from "../../utils/tx-analysis/types"; + +const verdictClass: Record = { + CONFIRMED: "tx-verdict tx-verdict--confirmed", + OPEN: "tx-verdict tx-verdict--open", + INSUFFICIENT: "tx-verdict tx-verdict--insufficient", +}; + +interface Props { + verdict: Verdict; + onOpenDeepDive?: () => void; +} + +export const VerdictCard: React.FC = ({ verdict, onOpenDeepDive }) => { + return ( +
+
+

{verdict.verdict}

+ {(verdict.confidence * 100).toFixed(0)}% confidence +
+ + {verdict.coreContradiction ? ( +
+

Core Contradiction

+
+
Expected
{verdict.coreContradiction.expected}
+
Actual
{verdict.coreContradiction.actual}
+
+
+ ) : null} + + {verdict.causalChain.length > 0 ? ( +
+

Causal Chain

+
    + {verdict.causalChain.map((s) => ( +
  1. {s.step} — {s.description} {s.evidenceId}
  2. + ))} +
+
+ ) : null} + + {verdict.gates.length > 0 ? ( +
+

Gates

+
    + {verdict.gates.map((g) => ( +
  • {g.name}{g.bypassedBy ? ` — bypassed by ${g.bypassedBy}` : ""}
  • + ))} +
+
+ ) : null} + + {verdict.riskBound ? ( +
+

Risk Upper Bound

+

{verdict.riskBound.upperBoundEth} ETH — {verdict.riskBound.rationale}

+
+ ) : null} + + {verdict.missingEvidence.length > 0 ? ( +
+

Missing Evidence

+
    {verdict.missingEvidence.map((m, i) =>
  • {m}
  • )}
+
+ ) : null} + + {onOpenDeepDive && !verdict.deepDive ? ( + + ) : null} +
+ ); +}; diff --git a/src/components/tx-analysis/hexkitTheme.tsx b/src/components/tx-analysis/hexkitTheme.tsx new file mode 100644 index 0000000..8d8ea2b --- /dev/null +++ b/src/components/tx-analysis/hexkitTheme.tsx @@ -0,0 +1,209 @@ +import React from "react"; + +export const HEXKIT_CSS = ` +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:ital,wght@0,400;0,500;0,700;1,400&display=swap'); + +.hk-root.dark { + --bg-primary: #0a0a0a; + --bg-secondary: #171717; + --bg-tertiary: #262626; + --bg-elevated: #1c1c1c; + --bg-glass: rgba(23,23,23,0.9); + --bg-card: rgba(38,38,38,0.95); + + --text-primary: #fafafa; + --text-secondary: #a1a1aa; + --text-tertiary: #71717a; + --text-muted: #52525b; + --text-accent: #d4d4d4; + + --border-primary: rgba(255,255,255,0.10); + --border-secondary: rgba(255,255,255,0.05); + --border-accent: rgba(255,255,255,0.30); + --border-focus: rgba(255,255,255,0.50); + + --accent-primary-10: rgba(255,255,255,0.10); + --accent-primary-20: rgba(255,255,255,0.20); + --accent-primary-30: rgba(255,255,255,0.30); + + --success: #22c55e; + --warning: #f59e0b; + --error: #ef4444; + + --font-body: 'Inter', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Consolas, monospace; + + background: var(--bg-primary); + color: var(--text-primary); + font-family: var(--font-body); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-feature-settings: 'cv11','ss01'; +} + +.hk-root * { box-sizing: border-box; } + +.hk-root .mono { font-family: var(--font-mono); } +.hk-root .brand-wordmark { + font-family: var(--font-mono); + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} +.hk-root .label-caps { + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 11px; + color: var(--text-tertiary); + font-weight: 500; +} + +.hk-root .constellation { + position: absolute; inset: 0; pointer-events: none; z-index: 0; + background-image: + radial-gradient(circle at 1px 1px, rgba(255,255,255,0.055) 1px, transparent 0), + radial-gradient(circle at 18px 18px, rgba(255,255,255,0.03) 1px, transparent 0); + background-size: 36px 36px, 72px 72px; + -webkit-mask-image: radial-gradient(ellipse at 50% 0%, black 55%, transparent 95%); + mask-image: radial-gradient(ellipse at 50% 0%, black 55%, transparent 95%); +} + +.hk-root .topbar { + position: sticky; top: 0; z-index: 10; + background: var(--bg-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-bottom: 1px solid var(--border-primary); +} + +.hk-root .hk-card { + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 12px; + box-shadow: 0 1px 2px rgba(0,0,0,0.3); +} +.hk-root .hk-card-elevated { + background: var(--bg-elevated); + border: 1px solid var(--border-primary); + border-radius: 12px; + box-shadow: 0 10px 15px rgba(0,0,0,0.3); +} +.hk-root .hk-card-accent { + background: rgba(255,255,255,0.05); + border: 1px solid var(--accent-primary-20); + border-radius: 12px; +} +.hk-root .hk-card-glass { + background: var(--bg-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--border-primary); + border-radius: 12px; +} + +.hk-root .hk-btn { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + font-family: var(--font-body); font-weight: 500; font-size: 13px; + border-radius: 8px; padding: 9px 14px; + border: 1px solid transparent; cursor: pointer; + transition: all 150ms ease; white-space: nowrap; line-height: 1; +} +.hk-root .hk-btn-primary { background: #fafafa; color: #0a0a0a; } +.hk-root .hk-btn-primary:hover { background: rgba(250,250,250,0.9); } +.hk-root .hk-btn-primary:disabled { background: rgba(250,250,250,0.3); color: rgba(10,10,10,0.5); cursor: not-allowed; } +.hk-root .hk-btn-secondary { background: #262626; color: #fafafa; border-color: rgba(255,255,255,0.10); } +.hk-root .hk-btn-secondary:hover { background: #2e2e2e; } +.hk-root .hk-btn-outline { background: transparent; color: #fafafa; border-color: rgba(255,255,255,0.20); } +.hk-root .hk-btn-outline:hover { background: rgba(255,255,255,0.05); } +.hk-root .hk-btn-ghost { background: transparent; color: #a1a1aa; } +.hk-root .hk-btn-ghost:hover { background: #262626; color: #fafafa; } +.hk-root .hk-btn:active:not(:disabled) { transform: scale(0.96); } + +.hk-root .hk-pill { + display: inline-flex; align-items: center; gap: 6px; + padding: 3px 10px; border-radius: 9999px; + font-size: 11px; font-weight: 500; + border: 1px solid; line-height: 1; +} +.hk-root .pill-default { background: #262626; color: #a1a1aa; border-color: rgba(255,255,255,0.10); } +.hk-root .pill-success { background: rgba(34,197,94,0.18); color: #bbf7d0; border-color: rgba(34,197,94,0.30); } +.hk-root .pill-warning { background: rgba(245,158,11,0.15); color: #facc15; border-color: rgba(245,158,11,0.35); } +.hk-root .pill-error { background: rgba(239,68,68,0.18); color: #fecaca; border-color: rgba(239,68,68,0.35); } +.hk-root .pill-accent { background: rgba(255,255,255,0.08); color: #ffffff; border-color: rgba(255,255,255,0.20); } +.hk-root .pill-mono { + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.hk-root .dot { width: 6px; height: 6px; border-radius: 9999px; background: currentColor; } +.hk-root .dot-glow { box-shadow: 0 0 8px currentColor; } + +.hk-root .hk-kbd { + display: inline-flex; align-items: center; justify-content: center; + min-width: 22px; height: 22px; padding: 0 6px; + border: 1px solid rgba(255,255,255,0.15); background: #262626; color: #a1a1aa; + border-radius: 4px; font-family: var(--font-mono); font-size: 11px; +} + +.hk-root .hk-input { + width: 100%; + background: #171717; + border: 1px solid rgba(255,255,255,0.10); + border-radius: 12px; + padding: 11px 14px; + color: #fafafa; + font-family: var(--font-body); + font-size: 13px; outline: none; + transition: all 150ms ease; +} +.hk-root .hk-input:focus { border-color: rgba(255,255,255,0.50); box-shadow: 0 0 0 2px rgba(255,255,255,0.20); } + +.hk-root .divider-h { height: 1px; background: var(--border-primary); width: 100%; } +.hk-root .divider-dash { border-top: 1px dashed var(--border-primary); width: 100%; } +`; + +interface HexKitRootProps { + children: React.ReactNode; + className?: string; + style?: React.CSSProperties; +} + +export const HexKitRoot: React.FC = ({ children, className, style }) => ( +
+ + {children} +
+); + +interface IconProps { + size?: number; +} + +export const HKI = { + arrowUp: (p: IconProps = {}) => ( + + ), + arrowDown: (p: IconProps = {}) => ( + + ), + arrowRight: (p: IconProps = {}) => ( + + ), + copy: (p: IconProps = {}) => ( + + ), + hex: (p: IconProps = {}) => ( + + ), +}; + +export const MicroLabel: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + {children} +); + +export const shortAddress = (addr: string | null | undefined, lead = 6, trail = 4): string => { + if (!addr) return "—"; + if (addr.length <= lead + trail + 1) return addr; + return `${addr.slice(0, lead)}…${addr.slice(-trail)}`; +}; diff --git a/src/components/tx-analysis/useHackAnalysis.ts b/src/components/tx-analysis/useHackAnalysis.ts new file mode 100644 index 0000000..f901383 --- /dev/null +++ b/src/components/tx-analysis/useHackAnalysis.ts @@ -0,0 +1,78 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { classify, type ClassifierLabel } from "../../utils/hack-analysis/classifier"; +import { retrieveAnalogs } from "../../utils/hack-analysis/retrieval"; +import { loadIncidents } from "../../utils/hack-analysis/incidents"; +import { runHackAnalysis } from "../../utils/hack-analysis/llm"; +import type { EvidencePacket } from "../../utils/tx-analysis/types"; +import type { HackAnalysis, Incident } from "../../utils/hack-analysis/types"; +import type { LlmInvokeFn } from "../../utils/tx-analysis/llm"; + +export type HackStatus = "idle" | "classifying" | "retrieving" | "llm" | "ready" | "error"; + +export interface UseHackAnalysisParams { + packet: EvidencePacket | null; + invoke: LlmInvokeFn; +} + +export function useHackAnalysis(params: UseHackAnalysisParams) { + const [status, setStatus] = useState("idle"); + const [error, setError] = useState(null); + const [labels, setLabels] = useState([]); + const [analogs, setAnalogs] = useState([]); + const [analysis, setAnalysis] = useState(null); + const abortRef = useRef(null); + + useEffect(() => { + abortRef.current?.abort(); + abortRef.current = null; + setStatus("idle"); + setError(null); + setLabels([]); + setAnalogs([]); + setAnalysis(null); + }, [params.packet]); + + const run = useCallback(async () => { + if (!params.packet) throw new Error("No evidence packet"); + abortRef.current?.abort(); + const controller = new AbortController(); + abortRef.current = controller; + try { + setError(null); + setStatus("classifying"); + const l = classify(params.packet); + setLabels(l); + setStatus("retrieving"); + const chainHint = params.packet.chainId === 1 ? "ethereum" : String(params.packet.chainId); + const a = retrieveAnalogs({ labels: l, corpus: loadIncidents(), chainHint, k: 3 }); + setAnalogs(a); + setStatus("llm"); + const result = await runHackAnalysis({ + packet: params.packet, + labels: l, + analogs: a, + invoke: params.invoke, + signal: controller.signal, + }); + setAnalysis(result); + setStatus("ready"); + return result; + } catch (e) { + const err = e instanceof Error ? e : new Error(String(e)); + if (err.name === "AbortError" || controller.signal.aborted) { + setStatus("idle"); + throw err; + } + setError(err); + setStatus("error"); + throw err; + } + }, [params.packet, params.invoke]); + + const cancel = useCallback(() => { + abortRef.current?.abort(); + abortRef.current = null; + }, []); + + return { status, error, labels, analogs, analysis, run, cancel }; +} diff --git a/src/components/tx-analysis/useHackTriage.ts b/src/components/tx-analysis/useHackTriage.ts new file mode 100644 index 0000000..c9f7f8b --- /dev/null +++ b/src/components/tx-analysis/useHackTriage.ts @@ -0,0 +1,245 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { useWriteContract, useAccount, useWalletClient, usePublicClient } from "wagmi"; +import type { PublicClient } from "viem"; +import { createCofheConfig, createCofheClient } from "@cofhe/sdk/web"; +import { sepolia as sepoliaChain } from "@cofhe/sdk/chains"; +import { FheTypes, Encryptable } from "@cofhe/sdk"; +import { PermitUtils } from "@cofhe/sdk/permits"; +import { packFeatures, type TriageResult } from "../../utils/hack-analysis/triage/cofhe"; +import type { EvidencePacket } from "../../utils/tx-analysis/types"; +import HackTriageArtifact from "../../abi/HackTriage.json"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const HackTriageAbi = (HackTriageArtifact as any).abi as readonly unknown[]; + +export type HackTriageStatus = + | "idle" + | "encrypting" + | "writing" + | "waiting-fhe" + | "decrypting" + | "ready" + | "error"; + +export interface UseHackTriageParams { + packet: EvidencePacket | null; + /** Deployed HackTriage contract address (from VITE_HACK_TRIAGE_ADDRESS) */ + contractAddress: `0x${string}`; +} + +export interface UseHackTriageReturn { + status: HackTriageStatus; + error: string | null; + /** Plaintext packed feature bits (for display/debug) */ + features: number | null; + /** Decrypted verdict: { classBits, severity } */ + verdict: TriageResult | null; + txHash: `0x${string}` | null; + /** Ciphertext handles that other contracts can read from HackTriage.latest — publishing these + * does not leak the underlying values; only permit holders can decrypt. */ + handles: { classBits: string; severity: string } | null; + contractAddress: `0x${string}`; + run: () => Promise; + cancel: () => void; +} + +/** + * Block-aware coprocessor wait: exits when BOTH conditions are met: + * - at least `minMs` milliseconds have elapsed + * - at least `minBlocks` new blocks have been mined + * Polls every 2 seconds. Respects `cancelledRef` to abort early. + */ +async function waitForCoprocessor( + publicClient: PublicClient, + cancelledRef: { current: boolean }, + minBlocks = 3, + minMs = 30_000, +): Promise { + const startBlock = await publicClient.getBlockNumber(); + const startTime = Date.now(); + while (true) { + if (cancelledRef.current) return; + const elapsed = Date.now() - startTime; + const current = await publicClient.getBlockNumber(); + const blocksElapsed = Number(current - startBlock); + if (elapsed >= minMs && blocksElapsed >= minBlocks) break; + await new Promise((r) => setTimeout(r, 2000)); + if (cancelledRef.current) return; + } +} + +export function useHackTriage({ + packet, + contractAddress, +}: UseHackTriageParams): UseHackTriageReturn { + const { writeContractAsync } = useWriteContract(); + const { data: walletClient } = useWalletClient(); + const { chain } = useAccount(); + const publicClient = usePublicClient(); + + const [status, setStatus] = useState("idle"); + const [features, setFeatures] = useState(null); + const [verdict, setVerdict] = useState(null); + const [txHash, setTxHash] = useState<`0x${string}` | null>(null); + const [handles, setHandles] = useState<{ classBits: string; severity: string } | null>(null); + const [error, setError] = useState(null); + const cancelled = useRef(false); + + useEffect(() => { + cancelled.current = true; + setStatus("idle"); + setFeatures(null); + setVerdict(null); + setTxHash(null); + setHandles(null); + setError(null); + }, [packet, contractAddress]); + + const run = useCallback(async () => { + cancelled.current = false; + + // Strict chain guard — must be on Sepolia (11155111), no fallback + if (chain?.id !== 11155111) { + setError("Wallet must be connected to Ethereum Sepolia (chainId 11155111)"); + setStatus("error"); + return; + } + + if (!packet) { + setError("no packet"); + setStatus("error"); + return; + } + if (!walletClient) { + setError("wallet not connected"); + setStatus("error"); + return; + } + if (!publicClient) { + setError("no public client"); + setStatus("error"); + return; + } + + const chainId = chain.id; + const account = walletClient.account.address; + + try { + setError(null); + setStatus("encrypting"); + + // 1. Pack feature bits + const bits = packFeatures(packet); + setFeatures(bits); + + // 2. Create CoFHE client for web (uses IndexedDB key storage by default) + const cofheConfig = createCofheConfig({ + environment: "web", + supportedChains: [sepoliaChain], + _internal: { + zkvWalletClient: walletClient, + }, + }); + const cofheClient = createCofheClient(cofheConfig); + + // 3. Connect the client with viem clients + await cofheClient.connect(publicClient, walletClient); + + // 4. Reuse an existing signed self-permit if one is still valid; otherwise sign a new one. + // Skipping this saves a wallet prompt on every run after the first. + const existingPermit = cofheClient.permits.getActivePermit(chainId, account); + const permitStillValid = !!existingPermit && PermitUtils.isValid(existingPermit).valid; + if (!permitStillValid) { + await cofheClient.permits.createSelf({ issuer: account }); + } + + // 5. Encrypt the packed feature bits as uint16 + const [encInput] = await cofheClient + .encryptInputs([Encryptable.uint16(BigInt(bits))]) + .setAccount(account) + .setChainId(chainId) + .execute(); + + if (cancelled.current) return; + + setStatus("writing"); + + // 6. Submit the encrypted features to the HackTriage contract + const hash = await writeContractAsync({ + address: contractAddress, + abi: HackTriageAbi, + functionName: "triage", + args: [ + { + ctHash: encInput.ctHash, + securityZone: encInput.securityZone, + utype: encInput.utype, + signature: encInput.signature as `0x${string}`, + }, + ], + }); + setTxHash(hash); + + if (cancelled.current) return; + + setStatus("waiting-fhe"); + + // 7. Wait for tx receipt (Sepolia can be slow under load, so give it a generous window) + await publicClient.waitForTransactionReceipt({ hash, timeout: 180_000 }); + + // 8. Wait for the CoFHE coprocessor to pick up and process the FHE task + await waitForCoprocessor(publicClient, cancelled); + + if (cancelled.current) return; + + setStatus("decrypting"); + + // 9. Read the ciphertext handles from the contract's public `latest` mapping + const latestResult = await publicClient.readContract({ + address: contractAddress, + abi: HackTriageAbi, + functionName: "latest", + args: [account], + }) as [bigint, bigint, bigint]; // [classBits handle, severity handle, blockNumber] + + const [classBitsHandle, severityHandle] = latestResult; + setHandles({ + classBits: `0x${classBitsHandle.toString(16).padStart(64, "0")}`, + severity: `0x${severityHandle.toString(16).padStart(64, "0")}`, + }); + + // 10. Decrypt the handles using the CoFHE SDK + const [classBitsVal, severityVal] = await Promise.all([ + cofheClient + .decryptForView(classBitsHandle, FheTypes.Uint16) + .setChainId(chainId) + .setAccount(account) + .withPermit() + .execute(), + cofheClient + .decryptForView(severityHandle, FheTypes.Uint8) + .setChainId(chainId) + .setAccount(account) + .withPermit() + .execute(), + ]); + + setVerdict({ + classBits: Number(classBitsVal), + severity: Number(severityVal), + }); + setStatus("ready"); + } catch (err) { + if (cancelled.current) return; + setError(err instanceof Error ? err.message : String(err)); + setStatus("error"); + } + }, [packet, walletClient, publicClient, chain, contractAddress, writeContractAsync]); + + const cancel = useCallback(() => { + cancelled.current = true; + setStatus((s) => (s === "ready" || s === "error" ? s : "idle")); + }, []); + + return { status, error, features, verdict, txHash, handles, contractAddress, run, cancel }; +} diff --git a/src/components/tx-analysis/useTxAnalysis.ts b/src/components/tx-analysis/useTxAnalysis.ts new file mode 100644 index 0000000..a588a62 --- /dev/null +++ b/src/components/tx-analysis/useTxAnalysis.ts @@ -0,0 +1,175 @@ +import { useCallback, useMemo, useRef, useState } from "react"; +import { extractEvidence } from "../../utils/tx-analysis/extractor"; +import { applyHeuristics } from "../../utils/tx-analysis/sieve"; +import { + runSimpleAnalysis, + runComplexAnalysis, + type LlmInvokeFn, +} from "../../utils/tx-analysis/llm"; +import { parseAndNormalizeVerdict } from "../../utils/tx-analysis/normalizeVerdict"; +import { runDeepDive } from "../../utils/tx-analysis/deepDive"; +import { txAnalysisStore } from "../../services/TxAnalysisStore"; +import type { EvidencePacket, Verdict } from "../../utils/tx-analysis/types"; +import type { BridgeSimulationResponsePayload } from "../../utils/transaction-simulation/types"; +import type { DeepDiveDependencies } from "../../utils/tx-analysis/deepDive"; +import { useLlmInvocation } from "../../hooks/useLlmInvocation"; +import { useLlmConfig } from "../../contexts/LlmConfigContext"; + +export interface UseTxAnalysisParams { + simulationId: string; + from: string; + to: string | null; + simulation: BridgeSimulationResponsePayload | null; + txHash: string | null; + deepDiveFetchers?: { + fetchVerifiedSource: DeepDiveDependencies["fetchVerifiedSource"]; + fetchHeimdallDecompile?: DeepDiveDependencies["fetchHeimdallDecompile"]; + }; +} + +export type AnalysisStatus = "idle" | "extracting" | "llm" | "deep_dive" | "ready" | "error"; + +export function useTxAnalysis(params: UseTxAnalysisParams) { + const [status, setStatus] = useState("idle"); + const [error, setError] = useState(null); + const [packet, setPacket] = useState(null); + const [verdict, setVerdict] = useState(null); + const [recordId, setRecordId] = useState(null); + const abortRef = useRef(null); + + const { invoke } = useLlmInvocation(); + const { config } = useLlmConfig(); + + const invokeFn: LlmInvokeFn = useMemo(() => async ({ system, user, responseSchema, signal }) => { + const provider = config.defaultProvider; + const model = config.providers[provider]?.model ?? "gemini-2.5-pro"; + const res = await invoke({ + task: "tx-analysis", + provider, + model, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + responseFormat: "json", + schema: responseSchema, + signal, + maxRetries: 1, + }); + return res.parsed ?? res.text; + }, [invoke, config.defaultProvider, config.providers]); + + const verdictInvokeFn: LlmInvokeFn = useMemo(() => async ({ system, user, signal }) => { + const provider = config.defaultProvider; + const model = config.providers[provider]?.model ?? "gemini-2.5-pro"; + // Skip schema validation inside useLlmInvocation; we normalize first then + // let runSimpleAnalysis/runComplexAnalysis run verdictSchema.parse on the + // normalized payload. This prevents schema_invalid errors on benign txs + // where the model returns synonyms ("BENIGN", "SAFE") or a confidence in + // 0-100 instead of 0-1. + const res = await invoke({ + task: "tx-analysis", + provider, + model, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + responseFormat: "json", + signal, + maxRetries: 1, + }); + const raw = res.parsed ?? res.text; + return parseAndNormalizeVerdict(raw); + }, [invoke, config.defaultProvider, config.providers]); + + const cancel = useCallback(() => { + abortRef.current?.abort(); + abortRef.current = null; + }, []); + + const runSimple = useCallback(async () => { + if (!params.simulation) throw new Error("No simulation attached to analysis"); + abortRef.current?.abort(); + const controller = new AbortController(); + abortRef.current = controller; + try { + setStatus("extracting"); + setError(null); + const raw = extractEvidence({ + simulationId: params.simulationId, + from: params.from, + to: params.to, + simulation: params.simulation, + txHash: params.txHash, + }); + const sieved = applyHeuristics(raw); + setPacket(sieved); + setStatus("llm"); + const result = await runSimpleAnalysis({ packet: sieved, signal: controller.signal, invoke: verdictInvokeFn }); + setVerdict(result); + setStatus("ready"); + const id = await txAnalysisStore.save({ + packet: sieved, + verdict: result, + depth: "simple", + rawPromptHash: result.promptHash, + }); + setRecordId(id); + return result; + } catch (e) { + const err = e instanceof Error ? e : new Error(String(e)); + if (err.name === "AbortError" || controller.signal.aborted) { + setStatus("idle"); + throw err; + } + setError(err); + setStatus("error"); + throw err; + } + }, [params.simulation, params.simulationId, params.from, params.to, params.txHash, verdictInvokeFn]); + + const runComplex = useCallback(async () => { + if (!packet) throw new Error("Run simple analysis first"); + if (!params.deepDiveFetchers) throw new Error("Deep-dive fetchers not configured"); + abortRef.current?.abort(); + const controller = new AbortController(); + abortRef.current = controller; + try { + setStatus("deep_dive"); + const ctx = await runDeepDive({ + packet, + fetchVerifiedSource: params.deepDiveFetchers.fetchVerifiedSource, + fetchHeimdallDecompile: params.deepDiveFetchers.fetchHeimdallDecompile, + }); + setStatus("llm"); + const result = await runComplexAnalysis({ + packet, + signal: controller.signal, + deepDiveContext: ctx.sources, + invoke: verdictInvokeFn, + }); + setVerdict(result); + setStatus("ready"); + const id = await txAnalysisStore.save({ + packet, + verdict: result, + depth: "complex", + rawPromptHash: result.promptHash, + }); + setRecordId(id); + return result; + } catch (e) { + const err = e instanceof Error ? e : new Error(String(e)); + if (err.name === "AbortError" || controller.signal.aborted) { + setStatus("ready"); + throw err; + } + setError(err); + setStatus("error"); + throw err; + } + }, [packet, params.deepDiveFetchers, verdictInvokeFn]); + + return { status, error, packet, verdict, recordId, runSimple, runComplex, cancel, invokeFn }; +} diff --git a/src/config/llmConfig.ts b/src/config/llmConfig.ts new file mode 100644 index 0000000..4048e08 --- /dev/null +++ b/src/config/llmConfig.ts @@ -0,0 +1,106 @@ +import type { LlmProvider } from "../utils/llm/types"; + +export interface LlmProviderSlot { + model: string; + customBaseUrl?: string; +} + +export interface LlmConfigSnapshot { + defaultProvider: LlmProvider; + providers: Record; + providerKeys: Partial>; + consentAcknowledged: boolean; + cacheSharingDefault: "off" | "on"; + version: number; +} + +export const DEFAULT_LLM_CONFIG: LlmConfigSnapshot = { + defaultProvider: "gemini", + providers: { + anthropic: { model: "claude-opus-4-7" }, + openai: { model: "gpt-5.4" }, + gemini: { model: "gemini-2.5-pro" }, + custom: { model: "", customBaseUrl: "" }, + }, + providerKeys: {}, + consentAcknowledged: false, + cacheSharingDefault: "off", + version: 1, +}; + +const PUBLIC_KEY = "web3-toolkit:llm-config"; +const SECRETS_KEY = "web3-toolkit:llm-secrets"; + +class LlmConfigManager { + private cache: LlmConfigSnapshot | null = null; + + getConfig(): LlmConfigSnapshot { + if (this.cache) return this.cache; + this.cache = this.load(); + return this.cache; + } + + saveConfig(patch: Partial): void { + const current = this.getConfig(); + const merged: LlmConfigSnapshot = { + ...current, + ...patch, + providers: { ...current.providers, ...(patch.providers ?? {}) }, + providerKeys: { ...current.providerKeys, ...(patch.providerKeys ?? {}) }, + }; + this.cache = merged; + if (typeof window !== "undefined") { + const { providerKeys, ...publicBlob } = merged; + void providerKeys; + window.localStorage.setItem(PUBLIC_KEY, JSON.stringify(publicBlob)); + window.localStorage.setItem(SECRETS_KEY, JSON.stringify(merged.providerKeys)); + window.dispatchEvent(new CustomEvent("llm-config-updated")); + } + } + + acknowledgeConsent(): void { + this.saveConfig({ consentAcknowledged: true }); + } + + reset(): void { + this.cache = { ...DEFAULT_LLM_CONFIG }; + if (typeof window !== "undefined") { + window.localStorage.removeItem(PUBLIC_KEY); + window.localStorage.removeItem(SECRETS_KEY); + window.dispatchEvent(new CustomEvent("llm-config-updated")); + } + } + + getProviderKey(provider: LlmProvider): string | undefined { + return this.getConfig().providerKeys[provider]; + } + + hasAnyUserKey(): boolean { + const keys = this.getConfig().providerKeys; + return Object.values(keys).some((v) => typeof v === "string" && v.length > 0); + } + + private load(): LlmConfigSnapshot { + if (typeof window === "undefined") return { ...DEFAULT_LLM_CONFIG }; + try { + const publicRaw = window.localStorage.getItem(PUBLIC_KEY); + const secretsRaw = window.localStorage.getItem(SECRETS_KEY); + const pub = publicRaw + ? (JSON.parse(publicRaw) as Partial) + : {}; + const secrets = secretsRaw + ? (JSON.parse(secretsRaw) as Partial) + : {}; + return { + ...DEFAULT_LLM_CONFIG, + ...pub, + providers: { ...DEFAULT_LLM_CONFIG.providers, ...(pub.providers ?? {}) }, + providerKeys: { ...secrets }, + }; + } catch { + return { ...DEFAULT_LLM_CONFIG }; + } + } +} + +export const llmConfigManager = new LlmConfigManager(); diff --git a/src/contexts/LlmConfigContext.tsx b/src/contexts/LlmConfigContext.tsx new file mode 100644 index 0000000..85c5f24 --- /dev/null +++ b/src/contexts/LlmConfigContext.tsx @@ -0,0 +1,53 @@ +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { + llmConfigManager, + type LlmConfigSnapshot, +} from "../config/llmConfig"; + +interface LlmConfigContextValue { + config: LlmConfigSnapshot; + configVersion: number; + saveConfig: (patch: Partial) => void; + acknowledgeConsent: () => void; + reset: () => void; + hasAnyUserKey: boolean; +} + +const Ctx = createContext(undefined); + +export const LlmConfigProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [config, setConfig] = useState(() => llmConfigManager.getConfig()); + const [version, setVersion] = useState(0); + + useEffect(() => { + const onUpdate = () => { + setConfig(llmConfigManager.getConfig()); + setVersion((v) => v + 1); + }; + window.addEventListener("llm-config-updated", onUpdate); + return () => window.removeEventListener("llm-config-updated", onUpdate); + }, []); + + const saveConfig = useCallback((patch: Partial) => { + llmConfigManager.saveConfig(patch); + }, []); + const acknowledgeConsent = useCallback(() => llmConfigManager.acknowledgeConsent(), []); + const reset = useCallback(() => llmConfigManager.reset(), []); + + const value = useMemo(() => ({ + config, + configVersion: version, + saveConfig, + acknowledgeConsent, + reset, + hasAnyUserKey: llmConfigManager.hasAnyUserKey(), + }), [config, version, saveConfig, acknowledgeConsent, reset]); + + return {children}; +}; + +export function useLlmConfig(): LlmConfigContextValue { + const v = useContext(Ctx); + if (!v) throw new Error("useLlmConfig must be used within LlmConfigProvider"); + return v; +} diff --git a/src/contexts/LlmConsentGateContext.tsx b/src/contexts/LlmConsentGateContext.tsx new file mode 100644 index 0000000..ce2f138 --- /dev/null +++ b/src/contexts/LlmConsentGateContext.tsx @@ -0,0 +1,52 @@ +import React, { createContext, useContext, useMemo, useRef, useState } from "react"; +import { useLlmConfig } from "./LlmConfigContext"; +import { LlmConsentModal } from "../components/llm/LlmConsentModal"; + +interface GateValue { + requestConsent: (providerName?: string) => Promise; +} + +const LlmConsentGateContext = createContext(null); + +export const LlmConsentGateProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const { config } = useLlmConfig(); + const [open, setOpen] = useState(false); + const [providerName, setProviderName] = useState(undefined); + const pending = useRef void>>([]); + + const value = useMemo(() => ({ + requestConsent: (name?: string) => { + if (config.consentAcknowledged) return Promise.resolve(true); + setProviderName(name); + setOpen(true); + return new Promise((resolve) => { + pending.current.push(resolve); + }); + }, + }), [config.consentAcknowledged]); + + const settle = (ack: boolean) => { + const waiters = pending.current; + pending.current = []; + setOpen(false); + for (const resolve of waiters) resolve(ack); + }; + + return ( + + {children} + settle(true)} + onClose={() => settle(false)} + /> + + ); +}; + +export function useLlmConsentGate(): GateValue { + const v = useContext(LlmConsentGateContext); + if (!v) throw new Error("useLlmConsentGate must be used inside "); + return v; +} diff --git a/src/contexts/SimulationContext.tsx b/src/contexts/SimulationContext.tsx index 9a02a69..0b97792 100644 --- a/src/contexts/SimulationContext.tsx +++ b/src/contexts/SimulationContext.tsx @@ -3,6 +3,16 @@ import type { SimulationResult } from "../types/transaction"; import type { TraceContract } from "../utils/traceAddressCollector"; import type { DecodedTraceRow } from "../utils/traceDecoder"; import { buildOpcodeTraceFromSnapshots } from "../utils/simulationArtifacts"; +import type { BridgeSimulationResponsePayload } from "../utils/transaction-simulation/types"; + +export interface TxAnalysisSubject { + simulationId: string; + from: string; + // null for contract-creation txs (e.g., Beanstalk). + to: string | null; + txHash: string | null; + simulation: BridgeSimulationResponsePayload; +} /** * Metadata from decoded trace that needs to persist across page refreshes. @@ -110,6 +120,9 @@ interface SimulationContextValue { setDecodedTraceMeta: (meta: DecodedTraceMeta | null) => void; /** Strip heavy trace data from currentSimulation after decoding is complete */ stripHeavyDataFromCurrentSimulation: () => void; + /** Subject of the Tx Analysis sub-tool (set when user clicks Summarize). */ + analysisSubject: TxAnalysisSubject | null; + setAnalysisSubject: (subject: TxAnalysisSubject | null) => void; } const SimulationContext = createContext( @@ -129,6 +142,8 @@ export const SimulationProvider: React.FC<{ children: React.ReactNode }> = ({ const [decodedTraceRows, setDecodedTraceRowsState] = useState(null); // Decoded trace metadata - persisted separately to survive page refresh const [decodedTraceMeta, setDecodedTraceMetaState] = useState(null); + // Tx-Captain analysis subject (set when user clicks Summarize). + const [analysisSubject, setAnalysisSubjectState] = useState(null); // Clear old localStorage data on mount to free space useEffect(() => { @@ -226,6 +241,10 @@ export const SimulationProvider: React.FC<{ children: React.ReactNode }> = ({ }); }, []); + const setAnalysisSubject = useCallback((subject: TxAnalysisSubject | null) => { + setAnalysisSubjectState(subject); + }, []); + const providerValue = useMemo(() => ({ currentSimulation, contractContext, @@ -240,6 +259,8 @@ export const SimulationProvider: React.FC<{ children: React.ReactNode }> = ({ decodedTraceMeta, setDecodedTraceMeta, stripHeavyDataFromCurrentSimulation, + analysisSubject, + setAnalysisSubject, }), [ currentSimulation, contractContext, @@ -254,6 +275,8 @@ export const SimulationProvider: React.FC<{ children: React.ReactNode }> = ({ decodedTraceMeta, setDecodedTraceMeta, stripHeavyDataFromCurrentSimulation, + analysisSubject, + setAnalysisSubject, ]); return ( diff --git a/src/contexts/debug/debugHelpers.ts b/src/contexts/debug/debugHelpers.ts index 88cd7d0..d980ec2 100644 --- a/src/contexts/debug/debugHelpers.ts +++ b/src/contexts/debug/debugHelpers.ts @@ -44,17 +44,6 @@ export { parseStorageWrite, } from './solidityStructLayout'; -export { - getSourceLineText, - deriveStructValueFromTrace, - deriveScalarStateValueFromTrace, - computeDynamicArrayDataSlot, - fillUnreadFieldsFromStorage, - matchesSourceLocation, - findNearestHookSnapshotIdBySource, - findNearestHookSnapshotIdByFunction, -} from './structStorageDecoding'; - // ── Gated debug logger ───────────────────────────────────────────────── const EDB_DEBUG_LOGS = import.meta.env.DEV && typeof localStorage !== 'undefined' && localStorage.getItem('edb:debugLogs') === '1'; diff --git a/src/contexts/debug/evalSnapshotResolver.ts b/src/contexts/debug/evalSnapshotResolver.ts index 164cd80..befd029 100644 --- a/src/contexts/debug/evalSnapshotResolver.ts +++ b/src/contexts/debug/evalSnapshotResolver.ts @@ -1,11 +1,6 @@ /** - * Eval Snapshot Resolver - * - * Extracted from useDebugEvaluation: logic for resolving evaluation-worthy - * snapshot IDs, scanning for hook snapshots, and waiting for live sessions. - * - * These are pure utility functions that receive dependencies as arguments, - * avoiding React hook coupling. + * Eval Snapshot Resolver: resolves evaluation-worthy snapshot IDs, scans + * for hook snapshots, and waits for live debug sessions. */ import type { @@ -36,7 +31,7 @@ export const EVAL_VARIABLE_HINT_CACHE_MAX = 1024; * so that evaluateExpressionInternal always returns a definitive error before the * outer timeout fires with a vague message. */ -export const EVAL_TOTAL_BUDGET_MS = 12000; +export const EVAL_TOTAL_BUDGET_MS = 40000; // ── LRU cache helper ────────────────────────────────────────────────── @@ -305,6 +300,7 @@ export interface ResolveEvalSnapshotDeps { export async function resolveEvalSnapshotId( deps: ResolveEvalSnapshotDeps, baseSnapshotId?: number | null, + traceEntrySnapshotRange?: { first: number; nextFirst: number | null } | null, ): Promise { const effectiveBase = baseSnapshotId ?? deps.currentSnapshotId; const activeSession = deps.sessionRef.current; @@ -334,77 +330,267 @@ export async function resolveEvalSnapshotId( } } - const snapshotListById = new Map(deps.snapshotList.map((snap) => [snap.id, snap])); - const maxLocalOffset = 50; - for (let offset = 1; offset <= maxLocalOffset; offset += 1) { - const prevId = effectiveBase - offset; - const nextId = effectiveBase + offset; + // When we have a targeted trace entry range AND it's far from effectiveBase, + // skip the local/remote ±50 scan (which would find wrong-context Hooks) + // and go directly to Phase 0. + const hasTargetedRange = traceEntrySnapshotRange && + traceEntrySnapshotRange.first >= 0 && + traceEntrySnapshotRange.first < (activeSession.totalSnapshots ?? 0); + const targetedRangeIsFar = hasTargetedRange && + Math.abs(traceEntrySnapshotRange!.first - effectiveBase) > 100; + + if (!targetedRangeIsFar) { + // Standard local scan: only when the targeted range is nearby or absent + const snapshotListById = new Map(deps.snapshotList.map((snap) => [snap.id, snap])); + const maxLocalOffset = 50; + for (let offset = 1; offset <= maxLocalOffset; offset += 1) { + const prevId = effectiveBase - offset; + const nextId = effectiveBase + offset; + + const prevCached = deps.snapshotCache.get(prevId); + if (prevCached?.type === 'hook' && matchesTraceId(prevCached.frameId, currentTraceId)) { + return prevId; + } + const nextCached = deps.snapshotCache.get(nextId); + if (nextCached?.type === 'hook' && matchesTraceId(nextCached.frameId, currentTraceId)) { + return nextId; + } - const prevCached = deps.snapshotCache.get(prevId); - if (prevCached?.type === 'hook' && matchesTraceId(prevCached.frameId, currentTraceId)) { - return prevId; - } - const nextCached = deps.snapshotCache.get(nextId); - if (nextCached?.type === 'hook' && matchesTraceId(nextCached.frameId, currentTraceId)) { - return nextId; + const prevList = snapshotListById.get(prevId); + if (prevList?.type === 'hook' && matchesTraceId(prevList.frameId, currentTraceId)) { + return prevId; + } + const nextList = snapshotListById.get(nextId); + if (nextList?.type === 'hook' && matchesTraceId(nextList.frameId, currentTraceId)) { + return nextId; + } } - const prevList = snapshotListById.get(prevId); - if (prevList?.type === 'hook' && matchesTraceId(prevList.frameId, currentTraceId)) { - return prevId; + const maxRemoteOffset = 10; + const maxSnapshotId = activeSession.totalSnapshots - 1; + for (let offset = 1; offset <= maxRemoteOffset; offset += 1) { + const candidates = [effectiveBase - offset, effectiveBase + offset].filter( + (id) => id >= 0 && id <= maxSnapshotId + ); + + for (const candidateId of candidates) { + try { + const response = await debugBridgeService.getSnapshot({ + sessionId: activeSession.sessionId, + snapshotId: candidateId, + }); + const resolved = enhanceHookSnapshot(response.snapshot, deps.sourceFilesRef.current); + deps.setSnapshotCache((prev) => { const next = new Map(prev); next.set(candidateId, resolved); if (next.size > 500) { const sortedKeys = [...next.keys()].sort((a, b) => a - b); sortedKeys.slice(0, next.size - 500).forEach(k => next.delete(k)); } return next; }); + if (resolved.type === 'hook' && matchesTraceId(resolved.frameId, currentTraceId)) { + return candidateId; + } + } catch { + // Ignore and continue searching + } + } } - const nextList = snapshotListById.get(nextId); - if (nextList?.type === 'hook' && matchesTraceId(nextList.frameId, currentTraceId)) { - return nextId; + + const nearestFromLoaded = findNearestHookSnapshotId( + deps.snapshotList, + deps.snapshotCache, + effectiveBase, + currentTraceId + ); + if (nearestFromLoaded !== null) { + return nearestFromLoaded; } } - const maxRemoteOffset = 25; - const maxSnapshotId = activeSession.totalSnapshots - 1; - for (let offset = 1; offset <= maxRemoteOffset; offset += 1) { - const candidates = [effectiveBase - offset, effectiveBase + offset].filter( - (id) => id >= 0 && id <= maxSnapshotId - ); + if (deps.snapshotList.length < activeSession.totalSnapshots || targetedRangeIsFar) { + const total = activeSession.totalSnapshots; - for (const candidateId of candidates) { - try { - const response = await debugBridgeService.getSnapshot({ + // Phase 0: Trace-entry-targeted scan — if we know the snapshot range for + // the current call frame, sample it sparsely to find Hook snapshots. + // Hook snapshots with populated locals tend to cluster in narrow bands, + // so we first sample ~50 points across the range, then refine around + // any Hook we find. This covers ranges of thousands of snapshots in + // ~2-3 batch RPCs instead of scanning every individual snapshot. + if (import.meta.env.DEV) console.log('[resolveEvalSnapshotId] Phase 0 check — range:', traceEntrySnapshotRange, 'effectiveBase:', effectiveBase, 'targetedRangeIsFar:', targetedRangeIsFar); + if (traceEntrySnapshotRange && traceEntrySnapshotRange.first >= 0 && traceEntrySnapshotRange.first < total) { + const rangeStart = traceEntrySnapshotRange.first; + const rangeEnd = traceEntrySnapshotRange.nextFirst !== null + ? Math.min(traceEntrySnapshotRange.nextFirst - 1, total - 1) + : total - 1; + const rangeSize = rangeEnd - rangeStart + 1; + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 — rangeStart=${rangeStart}, rangeEnd=${rangeEnd}, rangeSize=${rangeSize}`); + const existingIds = new Set(deps.snapshotList.map(s => s.id)); + const mergedList = [...deps.snapshotList]; + + const mergeBatch = async (startId: number, count: number) => { + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 mergeBatch — startId=${startId}, count=${count}`); + const response = await debugBridgeService.getSnapshotBatch({ sessionId: activeSession.sessionId, - snapshotId: candidateId, + startId, + count: Math.min(count, total - startId), }); - const resolved = enhanceHookSnapshot(response.snapshot, deps.sourceFilesRef.current); - deps.setSnapshotCache((prev) => { const next = new Map(prev); next.set(candidateId, resolved); if (next.size > 500) { const sortedKeys = [...next.keys()].sort((a, b) => a - b); sortedKeys.slice(0, next.size - 500).forEach(k => next.delete(k)); } return next; }); - if (resolved.type === 'hook' && matchesTraceId(resolved.frameId, currentTraceId)) { - return candidateId; + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 mergeBatch — fetched ${response.snapshots.length} snapshots`); + for (const snapshot of response.snapshots) { + if (!existingIds.has(snapshot.id)) { + mergedList.push(snapshot); + existingIds.add(snapshot.id); + } + } + }; + + try { + // Sparse-sample across the range. Each edb_getSnapshotInfo call goes + // through a remote proxy (~1-2s RTT) but runs in batches of 25 concurrent. + // Phase 0 only needs to find ANY Hook snapshot (the caller's source + // breakpoint strategy handles precise resolution), so 25 samples suffice. + const SAMPLE_CAP = Math.min(25, rangeSize); + const step = Math.max(1, Math.ceil(rangeSize / SAMPLE_CAP)); + const sampleIds: number[] = []; + for (let id = rangeStart; id <= rangeEnd; id += step) { + sampleIds.push(id); + } + if (!sampleIds.includes(rangeEnd)) sampleIds.push(rangeEnd); + + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 — sampling ${sampleIds.length} IDs across [${rangeStart}, ${rangeEnd}] (step=${step})`); + + const sampleResponse = await debugBridgeService.getSnapshotsBySparseIds( + activeSession.sessionId, + sampleIds, + ); + for (const snapshot of sampleResponse.snapshots) { + if (!existingIds.has(snapshot.id)) { + mergedList.push(snapshot); + existingIds.add(snapshot.id); + } + } + + mergedList.sort((a, b) => a.id - b.id); + deps.setSnapshotList(mergedList); + + // Find all Hook snapshots within the trace entry range. + // Hook snapshots at or very near rangeStart are typically function + // entry points with empty locals — prefer Hooks further into the range. + const hookIdsInRange = mergedList + .filter(s => + s.type === 'hook' && + s.id >= rangeStart && + s.id <= rangeEnd && + matchesTraceId(s.frameId, currentTraceId) + ) + .map(s => s.id) + .sort((a, b) => a - b); + + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 — merged ${mergedList.length} snapshots, hookIdsInRange: [${hookIdsInRange.join(',')}]`); + + // Prefer Hooks NOT at the entry point (rangeStart ± 5) + const nonEntryHooks = hookIdsInRange.filter(id => Math.abs(id - rangeStart) > 5); + const bestCandidates = nonEntryHooks.length > 0 ? nonEntryHooks : hookIdsInRange; + // Pick the one nearest to effectiveBase + let nearestFromTargeted: number | null = null; + if (bestCandidates.length > 0) { + nearestFromTargeted = bestCandidates.reduce((best, id) => + Math.abs(id - effectiveBase) < Math.abs(best - effectiveBase) ? id : best + ); + } + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 — nearestFromTargeted=${nearestFromTargeted} (nonEntry: ${nonEntryHooks.length})`); + + // If only entry-point Hooks found and range is large, probe for Hooks + // with populated locals by calling edb_evaluateExpression('this') at + // evenly-spaced probe points. This is 1 RPC per probe and is much more + // reliable than sparse snapshot-info sampling (which misses narrow Hook + // clusters). We use 10 probe points for ~10 concurrent RPCs. + if (nonEntryHooks.length === 0 && hookIdsInRange.length > 0 && rangeSize > 100) { + const PROBE_COUNT = 10; + const probeStep = Math.max(1, Math.floor(rangeSize / PROBE_COUNT)); + const probeIds: number[] = []; + for (let id = rangeStart + probeStep; id <= rangeEnd; id += probeStep) { + probeIds.push(id); + } + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 — eval-probing ${probeIds.length} points in [${rangeStart}, ${rangeEnd}]`); + + try { + // Probe concurrently — edb_evaluateExpression returns quickly for + // opcode snapshots (immediate error) and for Hook snapshots with + // locals (immediate success). + const probeResults = await Promise.allSettled( + probeIds.map(async (id) => { + const result = await debugBridgeService.evaluateExpression({ + sessionId: activeSession.sessionId, + snapshotId: id, + expression: 'this', + }); + return { id, success: result.result.success, isOpcode: result.result.error?.includes('opcode') }; + }) + ); + const hookProbeIds = probeResults + .filter((r): r is PromiseFulfilledResult<{ id: number; success: boolean; isOpcode: boolean | undefined }> => + r.status === 'fulfilled' && r.value.success + ) + .map(r => r.value.id); + + if (hookProbeIds.length > 0) { + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0 — eval-probe found Hook snapshots at: [${hookProbeIds.join(',')}]`); + // Pick nearest to effectiveBase + nearestFromTargeted = hookProbeIds.reduce((best, id) => + Math.abs(id - effectiveBase) < Math.abs(best - effectiveBase) ? id : best + ); + } + } catch { + // Probing failed, use what we have + } + } + + // Refine around the chosen Hook to find the best nearby one. + // Skip refinement when the eval-probe already confirmed Hook snapshots + // — they are verified to support eval and the refine batch (~51 RPCs) + // would just add latency without improving the result. + const evalProbeConfirmed = nonEntryHooks.length === 0 && hookIdsInRange.length > 0; + if (nearestFromTargeted !== null && step > 1 && !evalProbeConfirmed) { + const REFINE_WINDOW = 25; + const refineStart = Math.max(rangeStart, nearestFromTargeted - REFINE_WINDOW); + const refineEnd = Math.min(rangeEnd, nearestFromTargeted + REFINE_WINDOW); + try { + await mergeBatch(refineStart, refineEnd - refineStart + 1); + mergedList.sort((a, b) => a.id - b.id); + deps.setSnapshotList(mergedList); + // Re-find within refined data, still preferring non-entry hooks + const refinedHooks = mergedList + .filter(s => + s.type === 'hook' && + s.id >= refineStart && + s.id <= refineEnd && + matchesTraceId(s.frameId, currentTraceId) && + Math.abs(s.id - rangeStart) > 5 + ) + .map(s => s.id); + if (refinedHooks.length > 0) { + nearestFromTargeted = refinedHooks.reduce((best, id) => + Math.abs(id - effectiveBase) < Math.abs(best - effectiveBase) ? id : best + ); + } + } catch { + // Refinement failed, use the sample result + } + } + + if (nearestFromTargeted !== null) { + if (import.meta.env.DEV) console.log(`[resolveEvalSnapshotId] Phase 0: returning Hook snapshot ${nearestFromTargeted} via trace entry range [${rangeStart}, ${rangeEnd}]`); + return nearestFromTargeted; } } catch { - // Ignore and continue searching + // Targeted fetch failed, fall through to centered window } } - } - const nearestFromLoaded = findNearestHookSnapshotId( - deps.snapshotList, - deps.snapshotCache, - effectiveBase, - currentTraceId - ); - if (nearestFromLoaded !== null) { - return nearestFromLoaded; - } - - if (deps.snapshotList.length < activeSession.totalSnapshots) { - const MAX_SCAN_WINDOW = 200; - const total = activeSession.totalSnapshots; - const windowSize = Math.min(MAX_SCAN_WINDOW, total); - let startId = Math.max(0, effectiveBase - Math.floor(windowSize / 2)); - if (startId + windowSize > total) startId = Math.max(0, total - windowSize); - const count = Math.min(windowSize, total - startId); + // Phase 1: Centered window scan (handles nearby Hooks) + const MAX_CENTERED_WINDOW = 50; + const centeredSize = Math.min(MAX_CENTERED_WINDOW, total); + let centeredStart = Math.max(0, effectiveBase - Math.floor(centeredSize / 2)); + if (centeredStart + centeredSize > total) centeredStart = Math.max(0, total - centeredSize); try { const response = await debugBridgeService.getSnapshotBatch({ sessionId: activeSession.sessionId, - startId, - count, + startId: centeredStart, + count: Math.min(centeredSize, total - centeredStart), }); const existingIds = new Set(deps.snapshotList.map(s => s.id)); const mergedList = [...deps.snapshotList]; @@ -428,6 +614,79 @@ export async function resolveEvalSnapshotId( } catch { // Ignore and fall through } + + // Phase 2: Sparse sampling — sample ~100 evenly-spaced snapshots across + // the entire session to find Hook snapshot regions. When a Hook is found + // that matches the traceId, fetch a small window around it to find the + // best candidate. This covers large gaps (thousands of steps) in just + // ~100 RPC calls instead of scanning every individual snapshot. + if (total > MAX_CENTERED_WINDOW) { + const SAMPLE_COUNT = Math.min(25, total); + const step = Math.max(1, Math.floor(total / SAMPLE_COUNT)); + const sampleIds: number[] = []; + for (let id = 0; id < total; id += step) { + sampleIds.push(id); + } + if (sampleIds[sampleIds.length - 1] !== total - 1) { + sampleIds.push(total - 1); + } + + try { + const sampleResponse = await debugBridgeService.getSnapshotsBySparseIds( + activeSession.sessionId, + sampleIds, + ); + const existingIds = new Set(deps.snapshotList.map(s => s.id)); + const mergedList = [...deps.snapshotList]; + for (const snapshot of sampleResponse.snapshots) { + if (!existingIds.has(snapshot.id)) { + mergedList.push(snapshot); + existingIds.add(snapshot.id); + } + } + mergedList.sort((a, b) => a.id - b.id); + deps.setSnapshotList(mergedList); + + const hookSample = findNearestHookSnapshotId( + mergedList, + deps.snapshotCache, + effectiveBase, + currentTraceId + ); + if (hookSample !== null) { + // Refine — fetch a small window around the Hook sample + const REFINE_WINDOW = 25; + const refineStart = Math.max(0, hookSample - REFINE_WINDOW); + const refineCount = Math.min(REFINE_WINDOW * 2, total - refineStart); + try { + const refineResponse = await debugBridgeService.getSnapshotBatch({ + sessionId: activeSession.sessionId, + startId: refineStart, + count: refineCount, + }); + for (const snapshot of refineResponse.snapshots) { + if (!existingIds.has(snapshot.id)) { + mergedList.push(snapshot); + existingIds.add(snapshot.id); + } + } + mergedList.sort((a, b) => a.id - b.id); + deps.setSnapshotList(mergedList); + } catch { + // Refinement fetch failed, use sample result + } + const refined = findNearestHookSnapshotId( + mergedList, + deps.snapshotCache, + effectiveBase, + currentTraceId + ); + return refined ?? hookSample; + } + } catch { + // Sampling failed, fall through + } + } } if (currentTraceId !== null) { diff --git a/src/contexts/debug/useDebugEvaluation.ts b/src/contexts/debug/useDebugEvaluation.ts index 4e7e61b..c2455b0 100644 --- a/src/contexts/debug/useDebugEvaluation.ts +++ b/src/contexts/debug/useDebugEvaluation.ts @@ -195,6 +195,16 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi >(new Map()); const traceToLiveSnapshotCacheRef = useRef>(new Map()); + // Cache breakpoint hits per (bytecodeAddress, file, lineRange) so subsequent + // evals at the same execution-tree position skip the ~6s RPC entirely. + const breakpointHitsCacheRef = useRef>(new Map()); + + // Dedup lock: prevent concurrent evaluations of the same expression at the + // same snapshot. Without this, React StrictMode double-renders and other + // React lifecycle quirks cause 2-3 simultaneous eval calls that overwhelm + // the bridge with 3× the RPC load. + const evalInflightRef = useRef>>(new Map()); + // ── Wrapped resolver callbacks (delegate to extracted pure functions) ── const waitForLiveSessionReadyCb = useCallback( @@ -225,7 +235,8 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi ); const resolveEvalSnapshotIdCb = useCallback(async ( - baseSnapshotId = currentSnapshotId + baseSnapshotId = currentSnapshotId, + traceEntrySnapshotRange?: { first: number; nextFirst: number | null } | null, ): Promise => { return resolveEvalSnapshotId({ sessionRef, @@ -237,7 +248,7 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi snapshotList, setSnapshotList, sourceFilesRef, - }, baseSnapshotId); + }, baseSnapshotId, traceEntrySnapshotRange); }, [ currentSnapshotId, currentSnapshot, @@ -359,7 +370,7 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi // ── Core expression evaluation ─────────────────────────────────────── - const evaluateExpressionInternal = useCallback( + const evaluateExpressionInternalImpl = useCallback( async (expression: string): Promise => { const activeSession = sessionRef.current; if (!activeSession) { @@ -598,6 +609,289 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi evalSnapshotHintCacheRef.current.delete(evalContextKey); } } + // Look up the trace entry's snapshot range for targeted scanning. + // When available, this lets us jump directly to the correct region + // of the session instead of scanning outward from the current step. + // + // We check multiple sources for the trace entry: + // 1. currentTraceId — from the live snapshot's frameId + // 2. traceRowAtBase?.traceId — from the decoded trace row + // 3. Source-file matching — find an entry row whose source matches + // preferSourceFile (handles the common case where the user is + // viewing _verifySignatures in Verifier.sol but the live snapshot + // is in the calling contract's frame) + let traceEntrySnapshotRange: { first: number; nextFirst: number | null } | null = null; + let traceEntryBytecodeAddress: string | null = null; + if (decodedTraceRowsRef.current) { + // Only use call frame entries from the bridge trace data (negative IDs, + // created by analysisHelpers.ts) — their firstSnapshotId is a live + // session snapshot ID. WASM-decoded entries (positive IDs) use a + // different numbering that doesn't map to live session snapshots. + const allEntryRows = decodedTraceRowsRef.current.filter( + (row) => row.entryMeta && typeof row.firstSnapshotId === 'number' + && row.id < 0 + ); + + const buildRange = (entryRow: typeof allEntryRows[0]): { first: number; nextFirst: number | null } => { + let nextFirst: number | null = null; + const later = allEntryRows.filter( + r => r.firstSnapshotId! > entryRow.firstSnapshotId! + ); + if (later.length > 0) { + nextFirst = Math.min(...later.map(r => r.firstSnapshotId!)); + } + return { first: entryRow.firstSnapshotId!, nextFirst }; + }; + + // Find the target traceId — prefer the source-file match + let targetTraceId: number | null = null; + + if (preferSourceFile) { + const sourceFileName = preferSourceFile.split('/').pop()?.toLowerCase() || ''; + const sourceMatch = allEntryRows.find(r => { + const contractName = r.entryMeta?.targetContractName || r.entryMeta?.codeContractName || ''; + if (contractName && sourceFileName.includes(contractName.toLowerCase())) return true; + const entrySource = r.sourceFile || r.destSourceFile || ''; + if (entrySource && filePathMatches(entrySource, preferSourceFile)) return true; + return false; + }); + if (sourceMatch?.traceId !== undefined) { + targetTraceId = sourceMatch.traceId; + traceEntryBytecodeAddress = ( + sourceMatch.entryMeta?.codeAddress || + sourceMatch.entryMeta?.target || + null + )?.toLowerCase() ?? null; + } + } + + // Fallback: use currentTraceId or traceRowAtBase's traceId + if (targetTraceId === null) { + targetTraceId = traceRowAtBase?.traceId ?? currentTraceId; + // Also try to get bytecodeAddress from the fallback entry + if (traceEntryBytecodeAddress === null && targetTraceId !== null) { + const fallbackEntry = allEntryRows.find(r => r.traceId === targetTraceId); + if (fallbackEntry?.entryMeta) { + traceEntryBytecodeAddress = ( + fallbackEntry.entryMeta.codeAddress || + fallbackEntry.entryMeta.target || + null + )?.toLowerCase() ?? null; + } + } + } + + // Fetch the raw trace data from the bridge to get accurate + // first_snapshot_id values (decoded trace rows remap these IDs). + if (targetTraceId !== null) { + try { + const rawTrace = await debugBridgeService.getTrace(activeSession.sessionId); + const rawEntries = (rawTrace as any)?.inner ?? rawTrace?.entries ?? []; + const rawEntry = rawEntries.find((e: any) => e.id === targetTraceId); + const rawFsi = rawEntry?.first_snapshot_id ?? rawEntry?.firstSnapshotId; + if (typeof rawFsi === 'number' && rawFsi >= 0) { + // Find next entry's first_snapshot_id for range boundary + let nextFsi: number | null = null; + for (const e of rawEntries) { + const fsi = e.first_snapshot_id ?? e.firstSnapshotId; + if (typeof fsi === 'number' && fsi > rawFsi) { + if (nextFsi === null || fsi < nextFsi) nextFsi = fsi; + } + } + traceEntrySnapshotRange = { first: rawFsi, nextFirst: nextFsi }; + } + } catch { + // If trace fetch fails, fall back to decoded row data + const match = allEntryRows.find(r => r.traceId === targetTraceId); + if (match) { + traceEntrySnapshotRange = buildRange(match); + } + } + } + } + + if (import.meta.env.DEV) { + console.log('[eval] traceEntrySnapshotRange:', traceEntrySnapshotRange, 'preferSourceFile:', preferSourceFile, 'currentTraceId:', currentTraceId, 'traceRowAtBase?.traceId:', traceRowAtBase?.traceId); + if (decodedTraceRowsRef.current) { + const entryRows = decodedTraceRowsRef.current.filter(r => r.entryMeta && typeof r.firstSnapshotId === 'number'); + console.log('[eval] Entry rows with firstSnapshotId:', JSON.stringify(entryRows.map(r => ({ id: r.id, traceId: r.traceId, fsi: r.firstSnapshotId, cn: r.entryMeta?.targetContractName || r.contract, sf: r.sourceFile?.split('/').pop() })))); + } + } + + // ── Shared helper: fetch breakpoint hits, eval at candidates ── + // Used by the fast path (before Phase 0) and Strategy 1 (inside + // needsRangeProbe) to avoid ~160 lines of duplicated logic. + const ENTRY_POINT_SKIP = 5; // Skip first N snapshots in range (opcode-only entry prologue) + const BP_CACHE_MAX = 50; + const evalAtBreakpointHits = async (opts: { + sessionId: string; + bytecodeAddress: string; + filePath: string; + lineNumber: number; + lineRadius: number; + rangeStart: number; + rangeEnd: number; + baseSnapshotId: number; + expression: string; + bpCacheKey: string; + bpCache: Map; + notePrefix: string; + logPrefix: string; + }): Promise<{ snapshotId: number; result: EvalResult } | null> => { + const { + sessionId, bytecodeAddress, filePath, lineNumber, lineRadius, + rangeStart, rangeEnd, baseSnapshotId, expression, + bpCacheKey, bpCache, notePrefix, logPrefix, + } = opts; + let bpHits: number[]; + + const cachedHits = bpCache.get(bpCacheKey); + if (cachedHits) { + bpHits = cachedHits; + if (import.meta.env.DEV) console.log(`[eval] ${logPrefix}: using cached breakpoint hits`); + } else { + try { + const breakpoints: Array<{ location: { type: 'source'; bytecodeAddress: string; filePath: string; lineNumber: number } }> = []; + for (let line = Math.max(1, lineNumber - lineRadius); line <= lineNumber + lineRadius; line++) { + breakpoints.push({ + location: { type: 'source', bytecodeAddress, filePath, lineNumber: line }, + }); + } + if (import.meta.env.DEV) console.log(`[eval] ${logPrefix}: source breakpoint probe ${breakpoints.length} lines around L${lineNumber} in ${filePath}`); + const bpResponse = await debugBridgeService.getBreakpointHits({ sessionId, breakpoints }); + // Evict oldest entry if cache is full + if (bpCache.size >= BP_CACHE_MAX) { + const firstKey = bpCache.keys().next().value; + if (firstKey !== undefined) bpCache.delete(firstKey); + } + bpCache.set(bpCacheKey, bpResponse.hits); + bpHits = bpResponse.hits; + } catch { + if (import.meta.env.DEV) console.log(`[eval] ${logPrefix}: source breakpoint probe failed`); + return null; + } + } + + const hitsInRange = bpHits.filter(id => id > rangeStart + ENTRY_POINT_SKIP && id <= rangeEnd); + if (import.meta.env.DEV) console.log(`[eval] ${logPrefix}: hits in range [${rangeStart}, ${rangeEnd}]: [${hitsInRange.slice(0, 20).join(',')}] (${hitsInRange.length} total)`); + if (hitsInRange.length === 0) return null; + + // Sort by distance to baseSnapshotId, try closest first. + // Evaluate in small concurrent batches (5) for a balance between + // latency (parallel) and wasted RPCs (sequential). + const sorted = [...hitsInRange].sort((a, b) => Math.abs(a - baseSnapshotId) - Math.abs(b - baseSnapshotId)); + const candidates = sorted.slice(0, 10); + const BATCH_SIZE = 5; + for (let i = 0; i < candidates.length; i += BATCH_SIZE) { + if (budgetExhausted()) break; + const batch = candidates.slice(i, i + BATCH_SIZE); + const results = await Promise.allSettled( + batch.map(async (id) => { + const r = await debugBridgeService.evaluateExpression({ sessionId, snapshotId: id, expression }); + return { id, result: r.result }; + }) + ); + for (const er of results) { + if (er.status !== 'fulfilled') continue; + const { id, result: evalRes } = er.value; + // Check for session expiration + if (!evalRes.success && isSessionNotFoundError(new Error(evalRes.error || ''))) { + setSessionInvalid(true); + return { snapshotId: id, result: { success: false, error: SESSION_EXPIRED_ERROR } }; + } + if (evalRes.success && !isNullishEvalValue(evalRes.value)) { + if (import.meta.env.DEV) console.log(`[eval] ${logPrefix}: SUCCESS at snapshot ${id}`); + if (hasUnreadFieldsInValue(evalRes.value)) { + const filled = await fillUnreadFieldsFromStorage( + evalRes.value as SolValue, sessionId, id, rpcFallbackConfig ?? undefined + ); + return { + snapshotId: id, + result: withLiveSnapshotRemapNote({ + success: true, value: filled, + note: `Value resolved from step ${id} (${notePrefix} near step ${baseSnapshotId})`, + }), + }; + } + return { + snapshotId: id, + result: withLiveSnapshotRemapNote({ + ...evalRes, + note: `Value resolved from step ${id} (${notePrefix} near step ${baseSnapshotId})`, + }), + }; + } + } + } + return null; + }; + + // ── Fast path: source breakpoints BEFORE Phase 0 ── + // When we have bytecodeAddress + source location + snapshot range, fire + // source breakpoints first (~6s) and eval at the closest hits (~2s). + // This skips Phase 0 entirely (~13-15s), cutting total eval time from + // ~28s to ~10s in the common case. + let fastPathResult: EvalResult | null = null; + if ( + evalSnapshotId === null && + traceEntryBytecodeAddress && + preferSourceFile && + preferSourceLine !== null && + traceEntrySnapshotRange && + !budgetExhausted() + ) { + const rangeStart = traceEntrySnapshotRange.first; + const rangeEnd = traceEntrySnapshotRange.nextFirst !== null + ? Math.min(traceEntrySnapshotRange.nextFirst - 1, totalSnapshots - 1) + : totalSnapshots - 1; + + const LINE_RADIUS = 20; + // Include sessionId in cache key so hits don't leak across sessions + const bpCacheKey = `${activeSession.sessionId}|${traceEntryBytecodeAddress}|${preferSourceFile}|${preferSourceLine}|${LINE_RADIUS}`; + + const bpResult = await evalAtBreakpointHits({ + sessionId: activeSession.sessionId, + bytecodeAddress: traceEntryBytecodeAddress, + filePath: preferSourceFile, + lineNumber: preferSourceLine, + lineRadius: LINE_RADIUS, + rangeStart, + rangeEnd, + baseSnapshotId: resolvedBaseSnapshotId, + expression: expressionText, + bpCacheKey, + bpCache: breakpointHitsCacheRef.current, + notePrefix: 'fast-path source breakpoint', + logPrefix: 'Fast path', + }); + if (bpResult) { + evalSnapshotId = bpResult.snapshotId; + fastPathResult = bpResult.result; + setLimitedCacheEntry( + evalSnapshotHintCacheRef.current, + evalContextKey, + evalSnapshotId, + EVAL_SNAPSHOT_HINT_CACHE_MAX + ); + } else if (import.meta.env.DEV) { + console.log(`[eval] Fast path: no successful eval in range [${rangeStart}, ${rangeEnd}], falling through`); + } + } + + // If fast path already produced a result, return it immediately + if (fastPathResult) { + return fastPathResult; + } + + // Pre-populate the snapshot list via batch fetching FIRST — this is + // much cheaper than scanning individual snapshots (one round trip per + // batch of 25 vs one per snapshot). Once the snapshot list is loaded, + // the in-memory findNearest* helpers can locate Hook snapshots without + // any additional network calls. + if (evalSnapshotId === null && !budgetExhausted()) { + evalSnapshotId = await resolveEvalSnapshotIdCb(resolvedBaseSnapshotId, traceEntrySnapshotRange); + } + if (evalSnapshotId === null && preferSourceFile) { evalSnapshotId = findNearestHookSnapshotIdBySource( snapshotList, snapshotCache, resolvedBaseSnapshotId, currentTraceId, @@ -648,9 +942,6 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi } } - if (evalSnapshotId === null && !budgetExhausted()) { - evalSnapshotId = await resolveEvalSnapshotIdCb(resolvedBaseSnapshotId); - } if (evalSnapshotId === null && !budgetExhausted()) { const fallback = await scanForHookSnapshotCb( activeSession.sessionId, resolvedBaseSnapshotId, currentTraceId, targetedScanOffset, @@ -845,6 +1136,121 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi snapshotId: evalSnapshotId, expression: expressionText, }); + + if (import.meta.env.DEV) console.log(`[eval] Initial eval at snapshot ${evalSnapshotId} for '${expressionText}':`, response.result.success ? `success=${JSON.stringify(response.result.value)?.substring(0, 100)}` : `error=${response.result.error?.substring(0, 100)}`); + + // If eval succeeded but returned nullish for a simple variable, or failed + // with "variable not visible" or "opcode snapshot", and we have a trace + // entry range, try to find the right Hook snapshot and evaluate there. + // + // Strategy 1 (source breakpoints): Use edb_getBreakpointHits with source + // breakpoints at nearby lines — this is ONE RPC round that directly returns + // snapshot IDs where each source line is hit, bypassing the need for sparse + // sampling entirely. + // + // Strategy 2 (blind probe fallback): If source breakpoints fail (e.g. wrong + // file path format), probe the expression at evenly-spaced points. + const needsRangeProbe = + traceEntrySnapshotRange && + !budgetExhausted() && + simpleName && + ( + (response.result.success && isNullishEvalValue(response.result.value)) || + (!response.result.success && extractMissingVariableName(response.result.error)) || + (!response.result.success && response.result.error?.includes('opcode snapshot')) + ); + if (needsRangeProbe && traceEntrySnapshotRange) { + const rangeStart = traceEntrySnapshotRange.first; + const rangeEnd = traceEntrySnapshotRange.nextFirst !== null + ? Math.min(traceEntrySnapshotRange.nextFirst - 1, totalSnapshots - 1) + : totalSnapshots - 1; + const rangeSize = rangeEnd - rangeStart + 1; + + // --- Strategy 1: Source breakpoint hits (uses cache from fast path) --- + if (traceEntryBytecodeAddress && preferSourceFile && preferSourceLine !== null && !budgetExhausted()) { + const LINE_RADIUS = 20; + const bpCacheKey = `${activeSession.sessionId}|${traceEntryBytecodeAddress}|${preferSourceFile}|${preferSourceLine}|${LINE_RADIUS}`; + const s1Result = await evalAtBreakpointHits({ + sessionId: activeSession.sessionId, + bytecodeAddress: traceEntryBytecodeAddress, + filePath: preferSourceFile, + lineNumber: preferSourceLine, + lineRadius: LINE_RADIUS, + rangeStart, + rangeEnd, + baseSnapshotId: resolvedBaseSnapshotId, + expression: expressionText, + bpCacheKey, + bpCache: breakpointHitsCacheRef.current, + notePrefix: 'source breakpoint match', + logPrefix: 'Strategy 1', + }); + if (s1Result) { + setLimitedCacheEntry( + evalSnapshotHintCacheRef.current, + evalContextKey, + s1Result.snapshotId, + EVAL_SNAPSHOT_HINT_CACHE_MAX + ); + return s1Result.result; + } + } + + // --- Strategy 2: Blind eval probe fallback --- + // Evaluate the actual expression at evenly-spaced points within the range. + // Use 200 probes (step ≈ 20) which is dense enough to hit clusters down + // to ~20 IDs wide. For 7-wide clusters this may still miss, but combined + // with Strategy 1 it provides good coverage. + if (!budgetExhausted()) { + const PROBE_COUNT = 200; + const probeStep = Math.max(1, Math.floor(rangeSize / PROBE_COUNT)); + const probeIds: number[] = []; + // Start from rangeStart + probeStep to skip entry point + for (let id = rangeStart + probeStep; id <= rangeEnd; id += probeStep) { + probeIds.push(id); + } + if (import.meta.env.DEV) console.log(`[eval] Blind range probe: ${probeIds.length} points in [${rangeStart}, ${rangeEnd}] step=${probeStep} for '${expressionText}'`); + + // Batch concurrent eval calls to avoid overwhelming the bridge + const EVAL_CONCURRENCY = 25; + for (let i = 0; i < probeIds.length; i += EVAL_CONCURRENCY) { + if (budgetExhausted()) break; + const batch = probeIds.slice(i, i + EVAL_CONCURRENCY); + const probeResults = await Promise.allSettled( + batch.map(async (id) => { + const r = await debugBridgeService.evaluateExpression({ + sessionId: activeSession.sessionId, + snapshotId: id, + expression: expressionText, + }); + return { id, result: r.result }; + }) + ); + for (const pr of probeResults) { + if (pr.status !== 'fulfilled') continue; + const { id, result: probeResult } = pr.value; + if (probeResult.success && !isNullishEvalValue(probeResult.value)) { + if (import.meta.env.DEV) console.log(`[eval] Blind probe hit at snapshot ${id}`); + if (hasUnreadFieldsInValue(probeResult.value)) { + const filled = await fillUnreadFieldsFromStorage( + probeResult.value as SolValue, activeSession.sessionId, id, rpcFallbackConfig ?? undefined + ); + return withLiveSnapshotRemapNote({ + success: true, + value: filled, + note: `Value resolved from step ${id} (nearest source-level snapshot to current step ${resolvedBaseSnapshotId})`, + }); + } + return withLiveSnapshotRemapNote({ + ...probeResult, + note: `Value resolved from step ${id} (nearest source-level snapshot to current step ${resolvedBaseSnapshotId})`, + }); + } + } + } + } + } + if (response.result.success && isNullishEvalValue(response.result.value) && simpleName) { const variableMatch = await getSimpleNameHint(); if (variableMatch?.value && !isNullishEvalValue(variableMatch.value)) { @@ -1006,6 +1412,25 @@ export function useDebugEvaluation(state: DebugSharedState): DebugEvaluationActi ] ); + // Dedup wrapper: if the same expression is already being evaluated at the + // same snapshot, return the in-flight promise instead of starting a new eval. + const evaluateExpressionInternal = useCallback( + (expression: string): Promise => { + const dedupKey = `${expression}|${currentSnapshotId}`; + const inflight = evalInflightRef.current.get(dedupKey); + if (inflight) { + if (import.meta.env.DEV) console.log(`[eval] Dedup: reusing in-flight eval for '${expression}' at snapshot ${currentSnapshotId}`); + return inflight; + } + const promise = evaluateExpressionInternalImpl(expression).finally(() => { + evalInflightRef.current.delete(dedupKey); + }); + evalInflightRef.current.set(dedupKey, promise); + return promise; + }, + [evaluateExpressionInternalImpl, currentSnapshotId] + ); + // ── Watch expression management ────────────────────────────────────── const addWatchExpression = useCallback((expression: string) => { diff --git a/src/contexts/debug/useDebugPrep.ts b/src/contexts/debug/useDebugPrep.ts index 0322eb1..463717b 100644 --- a/src/contexts/debug/useDebugPrep.ts +++ b/src/contexts/debug/useDebugPrep.ts @@ -131,6 +131,12 @@ export function useDebugPrep( }, ).catch((err) => { console.error('[useDebugPrep] auto-connect failed:', err); + if (prepareIdRef.current === prepareId) { + setPrepState((prev) => ({ + ...prev, + error: 'Live session was evicted before connection completed. Click Open Debugger to use trace-based debugging.', + })); + } }); }; diff --git a/src/hooks/useContractInputs.ts b/src/hooks/useContractInputs.ts index c156ae2..202f3af 100644 --- a/src/hooks/useContractInputs.ts +++ b/src/hooks/useContractInputs.ts @@ -108,25 +108,20 @@ export function useContractInputs({ inputs, onValuesChange, onCalldataGenerated, const func = selectedFunctionRef.current; const currentInputs = inputsRef.current; if (onCalldataGeneratedRef.current && func && allValid) { - try { - import('ethers').then(({ ethers }) => { - const formattedArgs = currentInputs.map(input => { - const state = newStates[input.name]; - if (!state) return formatValueForContract(getDefaultValue(input.type), input.type); - return formatValueForContract(state.value, input.type); - }); - - const iface = new ethers.utils.Interface([func]); - const calldata = iface.encodeFunctionData(func.name, formattedArgs); - onCalldataGeneratedRef.current?.(calldata); - }).catch(error => { - console.error('Failed to generate calldata:', error); - onCalldataGeneratedRef.current?.("0x"); + import('ethers').then(({ ethers }) => { + const formattedArgs = currentInputs.map(input => { + const state = newStates[input.name]; + if (!state) return formatValueForContract(getDefaultValue(input.type), input.type); + return formatValueForContract(state.value, input.type); }); - } catch (error) { + + const iface = new ethers.utils.Interface([func]); + const calldata = iface.encodeFunctionData(func.name, formattedArgs); + onCalldataGeneratedRef.current?.(calldata); + }).catch(error => { console.error('Failed to generate calldata:', error); onCalldataGeneratedRef.current?.("0x"); - } + }); } }, 0); diff --git a/src/hooks/useLlmConsent.ts b/src/hooks/useLlmConsent.ts new file mode 100644 index 0000000..7f28035 --- /dev/null +++ b/src/hooks/useLlmConsent.ts @@ -0,0 +1,8 @@ +import { useCallback } from "react"; +import { useLlmConfig } from "../contexts/LlmConfigContext"; + +export function useLlmConsent() { + const { config, acknowledgeConsent } = useLlmConfig(); + const requestAck = useCallback(() => acknowledgeConsent(), [acknowledgeConsent]); + return { acknowledged: config.consentAcknowledged, requestAck }; +} diff --git a/src/hooks/useLlmInvocation.ts b/src/hooks/useLlmInvocation.ts new file mode 100644 index 0000000..b00302f --- /dev/null +++ b/src/hooks/useLlmInvocation.ts @@ -0,0 +1,293 @@ +import { useCallback } from "react"; +import { useLlmConfig } from "../contexts/LlmConfigContext"; +import { useLlmConsentGate } from "../contexts/LlmConsentGateContext"; +import { llmConfigManager } from "../config/llmConfig"; +import { + LlmError, + type LlmErrorClass, + type LlmProvider, + type LlmRequest, + type LlmResponse, +} from "../utils/llm/types"; +import { + parseAnthropicSseChunk, + parseGeminiSseChunk, + parseOpenAISseChunk, + type StreamEvent, +} from "../utils/llm/streamParser"; + +const PROVIDER_PATHS: Record, string> = { + anthropic: "/v1/messages", + openai: "/v1/chat/completions", + gemini: "/v1beta/models/:model:generateContent", +}; + +function classifyHttpError(status: number): LlmErrorClass { + if (status === 401 || status === 403) return "bad_key"; + if (status === 429) return "rate_limit"; + if (status >= 500) return "provider_down"; + if (status === 413 || status === 414) return "context_overflow"; + return "unknown"; +} + +function buildProviderBody(req: LlmRequest): Record { + switch (req.provider) { + case "anthropic": + return { + model: req.model, + max_tokens: 4096, + messages: req.messages + .filter((m) => m.role !== "system") + .map((m) => ({ role: m.role, content: m.content })), + system: req.messages.find((m) => m.role === "system")?.content, + }; + case "openai": + return { model: req.model, messages: req.messages }; + case "gemini": + return { + contents: req.messages.map((m) => ({ + role: m.role === "assistant" ? "model" : "user", + parts: [{ text: m.content }], + })), + }; + case "custom": + throw new LlmError( + "unauthorized_endpoint", + "custom provider must be invoked browser-direct, not through useLlmInvocation", + "custom", + false, + ); + } +} + +function stripJsonEnvelope(text: string): string { + const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/i); + if (fenced) return fenced[1].trim(); + const firstBrace = text.indexOf("{"); + const lastBrace = text.lastIndexOf("}"); + if (firstBrace >= 0 && lastBrace > firstBrace) { + return text.slice(firstBrace, lastBrace + 1); + } + return text.trim(); +} + +function extractText(provider: LlmProvider, upstream: any): string { + if (provider === "anthropic") { + const blocks = upstream?.content ?? []; + return blocks.map((b: any) => (b?.text ?? "")).join(""); + } + if (provider === "openai") { + return upstream?.choices?.[0]?.message?.content ?? ""; + } + if (provider === "gemini") { + const parts = upstream?.candidates?.[0]?.content?.parts ?? []; + return parts.map((p: any) => p?.text ?? "").join(""); + } + return ""; +} + +export function useLlmInvocation() { + const { config } = useLlmConfig(); + const { requestConsent } = useLlmConsentGate(); + + const invoke = useCallback(async ( + req: LlmRequest, + ): Promise> => { + if (!config.consentAcknowledged) { + const providerLabel = + req.provider === "gemini" ? "Gemini (hexkit proxy)" : + req.provider === "anthropic" ? "Anthropic" : + req.provider === "openai" ? "OpenAI" : + "a custom LLM endpoint"; + const ack = await requestConsent(providerLabel); + if (!ack) { + throw new LlmError("consent_required", "user declined consent", req.provider, false); + } + } + if (req.provider === "custom") { + throw new LlmError( + "unauthorized_endpoint", + "custom provider endpoints must be invoked browser-direct (not implemented yet)", + "custom", + false, + ); + } + + const path = PROVIDER_PATHS[req.provider].replace(":model", encodeURIComponent(req.model)); + const userKey = llmConfigManager.getProviderKey(req.provider); + const maxRetries = req.maxRetries ?? 2; + + let lastErr: LlmError | null = null; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + const res = await fetch("/api/llm-invoke", { + method: "POST", + headers: { + "content-type": "application/json", + ...(userKey ? { "x-user-api-key": userKey } : {}), + }, + body: JSON.stringify({ + provider: req.provider, + path, + body: buildProviderBody(req), + }), + signal: req.signal, + }); + if (!res.ok) { + const cls = classifyHttpError(res.status); + const detail = await res.text().catch(() => ""); + const err = new LlmError(cls, `${res.status}: ${detail}`, req.provider, cls === "provider_down" || cls === "rate_limit"); + if (!err.retryable || attempt === maxRetries) throw err; + lastErr = err; + continue; + } + const json = await res.json(); + const text = extractText(req.provider, json); + if (req.schema && req.responseFormat === "json") { + const jsonText = stripJsonEnvelope(text); + let parsed: unknown; + try { + parsed = JSON.parse(jsonText); + } catch { + const err = new LlmError( + "schema_invalid", + `model did not return valid JSON: ${jsonText.slice(0, 120)}`, + req.provider, + true, + ); + if (attempt === maxRetries) throw err; + lastErr = err; + continue; + } + const schemaResult = (req.schema as any).safeParse(parsed); + if (!schemaResult.success) { + const err = new LlmError( + "schema_invalid", + `schema validation failed: ${schemaResult.error.issues[0]?.message ?? "?"}`, + req.provider, + true, + ); + if (attempt === maxRetries) throw err; + lastErr = err; + continue; + } + return { + text, + parsed: schemaResult.data, + provider: req.provider, + model: req.model, + }; + } + return { text, provider: req.provider, model: req.model }; + } catch (err) { + if (err instanceof LlmError) { + if (!err.retryable || attempt === maxRetries) throw err; + lastErr = err; + continue; + } + if (err instanceof Error && err.name === "AbortError") throw err; + lastErr = new LlmError("network", (err as Error).message, req.provider, true); + if (attempt === maxRetries) throw lastErr; + } + } + throw lastErr ?? new LlmError("unknown", "unknown", req.provider, false); + }, [config.consentAcknowledged, requestConsent]); + + return { invoke }; +} + +export interface StreamHandle { + onText: (cb: (delta: string) => void) => void; + done: Promise; + abort: () => void; +} + +export function useLlmInvocationStream() { + const { config } = useLlmConfig(); + const { requestConsent } = useLlmConsentGate(); + + const invokeStream = useCallback((req: LlmRequest): StreamHandle => { + if (req.provider === "custom") { + throw new LlmError("unauthorized_endpoint", "custom provider must go browser-direct", "custom", false); + } + + const basePath = PROVIDER_PATHS[req.provider].replace(":model", encodeURIComponent(req.model)).replace("generateContent", "streamGenerateContent"); + // Gemini's REST streaming endpoint is `:streamGenerateContent?alt=sse`; without + // alt=sse it returns a buffered JSON array, not an SSE stream. Anthropic/OpenAI + // use `stream: true` in the body instead. + const path = req.provider === "gemini" ? `${basePath}?alt=sse` : basePath; + const userKey = llmConfigManager.getProviderKey(req.provider); + const textCbs: Array<(d: string) => void> = []; + const controller = new AbortController(); + + const done: Promise = (async () => { + if (!config.consentAcknowledged) { + const providerLabel = + req.provider === "gemini" ? "Gemini (hexkit proxy)" : + req.provider === "anthropic" ? "Anthropic" : + req.provider === "openai" ? "OpenAI" : + "a custom LLM endpoint"; + const ack = await requestConsent(providerLabel); + if (!ack) { + throw new LlmError("consent_required", "user declined consent", req.provider, false); + } + } + const res = await fetch("/api/llm-invoke", { + method: "POST", + headers: { + "content-type": "application/json", + ...(userKey ? { "x-user-api-key": userKey } : {}), + }, + body: JSON.stringify({ + provider: req.provider, + path, + body: + req.provider === "gemini" + ? buildProviderBody(req) + : { ...buildProviderBody(req), stream: true }, + }), + signal: controller.signal, + }); + + if (!res.ok) { + throw new LlmError(classifyHttpError(res.status), `${res.status}`, req.provider, false); + } + if (!res.body) throw new LlmError("network", "no response body", req.provider, false); + + const reader = res.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + let fullText = ""; + + const parse = (chunk: string): StreamEvent[] => + req.provider === "anthropic" ? parseAnthropicSseChunk(chunk) + : req.provider === "openai" ? parseOpenAISseChunk(chunk) + : parseGeminiSseChunk(chunk); + + while (true) { + const { value, done: readerDone } = await reader.read(); + if (readerDone) break; + buffer += decoder.decode(value, { stream: true }); + const splitAt = buffer.lastIndexOf("\n\n"); + if (splitAt < 0) continue; + const ready = buffer.slice(0, splitAt + 2); + buffer = buffer.slice(splitAt + 2); + for (const ev of parse(ready)) { + if (ev.type === "text") { + fullText += ev.value; + textCbs.forEach((cb) => cb(ev.value)); + } + } + } + return { text: fullText, provider: req.provider, model: req.model }; + })(); + + return { + onText: (cb) => { textCbs.push(cb); }, + done, + abort: () => controller.abort(), + }; + }, [config.consentAcknowledged, requestConsent]); + + return { invokeStream }; +} diff --git a/src/hooks/useTxPreview.ts b/src/hooks/useTxPreview.ts new file mode 100644 index 0000000..657991f --- /dev/null +++ b/src/hooks/useTxPreview.ts @@ -0,0 +1,209 @@ +import { useEffect, useRef, useState, useCallback } from "react"; +import { ethers } from "ethers"; +import { useNetworkConfig } from "../contexts/NetworkConfigContext"; +import type { ExtendedChain } from "../components/shared/NetworkSelector"; +import type { TxPreviewData, TxFetchStatus } from "../components/transaction-builder/types"; +import type { Chain } from "../types"; + +export const ALCHEMY_MISSING_KEY_NOTICE = + "Alchemy was selected without an API key. Switched back to App Default RPC."; +export const INFURA_MISSING_KEY_NOTICE = + "Infura was selected without a Project ID. Switched back to App Default RPC."; +export const RPC_AUTO_SWITCH_NOTICE_KEY = "web3-toolkit:rpc-auto-switch-notice"; + +export type RpcNoticeConfig = { + rpcMode: "DEFAULT" | "ALCHEMY" | "INFURA" | "CUSTOM"; + alchemyApiKey?: string; + infuraProjectId?: string; +}; + +export function formatReplayRpcError(rawError: string, networkName: string, mode: string): string { + const trimmed = rawError.trim(); + const lower = trimmed.toLowerCase(); + + if (lower.includes("could not detect network") || lower.includes("nonetwork")) { + if (mode === "DEFAULT") { + return `Could not connect to the App Default RPC for ${networkName}. Configure a custom RPC in Settings if this network remains unavailable.`; + } + return `Could not connect to the configured ${mode} RPC for ${networkName}. Check your API key or switch providers in Settings.`; + } + + if (lower.includes("403") || lower.includes("forbidden")) { + if (mode === "DEFAULT") { + return `The App Default RPC for ${networkName} rejected the request. Configure a custom RPC in Settings to continue.`; + } + return `The configured ${mode} RPC rejected the request. Check your API key or switch providers in Settings.`; + } + + return trimmed || "Failed to fetch transaction"; +} + +export function getMissingProviderNotice(config: RpcNoticeConfig): string | null { + if (config.rpcMode === "ALCHEMY" && !config.alchemyApiKey?.trim()) { + return ALCHEMY_MISSING_KEY_NOTICE; + } + if (config.rpcMode === "INFURA" && !config.infuraProjectId?.trim()) { + return INFURA_MISSING_KEY_NOTICE; + } + return null; +} + +export function clearPersistedRpcAutoSwitchNotice() { + if (typeof window === "undefined") return; + window.localStorage.removeItem(RPC_AUTO_SWITCH_NOTICE_KEY); + window.sessionStorage.removeItem(RPC_AUTO_SWITCH_NOTICE_KEY); +} + +export function getPersistedRpcAutoSwitchNotice(): string | null { + if (typeof window === "undefined") return null; + return ( + window.localStorage.getItem(RPC_AUTO_SWITCH_NOTICE_KEY) || + window.sessionStorage.getItem(RPC_AUTO_SWITCH_NOTICE_KEY) + ); +} + +export function shouldClearAutoSwitchNotice( + notice: string | null | undefined, + config: Pick, +): boolean { + if (notice === ALCHEMY_MISSING_KEY_NOTICE) { + return Boolean(config.alchemyApiKey?.trim()); + } + if (notice === INFURA_MISSING_KEY_NOTICE) { + return Boolean(config.infuraProjectId?.trim()); + } + return false; +} + +interface UseTxPreviewOptions { + txHash: string; + selectedNetwork: ExtendedChain; +} + +interface UseTxPreviewResult { + txPreview: TxPreviewData | null; + txFetchStatus: TxFetchStatus; + txFetchError: string | null; + rpcNotice: string | null; + reset: () => void; +} + +export function useTxPreview({ txHash, selectedNetwork }: UseTxPreviewOptions): UseTxPreviewResult { + const { config, resolveRpcUrl, saveConfig } = useNetworkConfig(); + + const [txPreview, setTxPreview] = useState(null); + const [txFetchStatus, setTxFetchStatus] = useState("idle"); + const [txFetchError, setTxFetchError] = useState(null); + const [rpcNotice, setRpcNotice] = useState(null); + const fetchAbortRef = useRef(null); + + const reset = useCallback(() => { + if (fetchAbortRef.current) { + fetchAbortRef.current.abort(); + fetchAbortRef.current = null; + } + setTxPreview(null); + setTxFetchStatus("idle"); + setTxFetchError(null); + }, []); + + useEffect(() => { + const trimmedHash = txHash.trim(); + + if (!trimmedHash || !/^0x[a-fA-F0-9]{64}$/.test(trimmedHash)) { + setTxPreview(null); + setTxFetchStatus("idle"); + setTxFetchError(null); + return; + } + + if (fetchAbortRef.current) { + fetchAbortRef.current.abort(); + } + + const abortController = new AbortController(); + fetchAbortRef.current = abortController; + + const timer = setTimeout(async () => { + if (abortController.signal.aborted) return; + + setTxFetchStatus("fetching"); + setTxFetchError(null); + setTxPreview(null); + + try { + const missingProviderNotice = getMissingProviderNotice(config); + if (missingProviderNotice) { + setRpcNotice(missingProviderNotice); + saveConfig({ rpcMode: "DEFAULT" }); + } + + const chainForRpc: Chain = { + id: selectedNetwork.id, + name: selectedNetwork.name, + rpcUrl: selectedNetwork.rpcUrl ?? "", + blockExplorer: selectedNetwork.blockExplorer ?? "", + } as Chain; + + const rpcResolution = resolveRpcUrl(chainForRpc.id, selectedNetwork.rpcUrl); + const rpcUrl = rpcResolution.url; + const persistedNotice = getPersistedRpcAutoSwitchNotice(); + if (shouldClearAutoSwitchNotice(persistedNotice, config)) { + clearPersistedRpcAutoSwitchNotice(); + } else if (persistedNotice) { + setRpcNotice(persistedNotice); + } else if (rpcResolution.note) { + setRpcNotice(rpcResolution.note); + } + + if (!rpcUrl) { + setTxFetchStatus("error"); + setTxFetchError( + rpcResolution.note || + `No RPC available for ${selectedNetwork.name}. Switch to App Default RPC or configure a custom RPC in Settings.`, + ); + return; + } + + const provider = new ethers.providers.JsonRpcProvider(rpcUrl); + const tx = await provider.getTransaction(trimmedHash); + + if (abortController.signal.aborted) return; + + if (!tx) { + setTxFetchStatus("not_found"); + setTxFetchError(`Transaction not found on ${selectedNetwork?.name || "this network"}`); + return; + } + + setTxPreview({ + from: tx.from, + to: tx.to ?? null, + value: tx.value?.toString() || "0", + data: tx.data || "0x", + blockNumber: tx.blockNumber ?? null, + nonce: tx.nonce, + }); + setTxFetchStatus("found"); + setTxFetchError(null); + } catch (err: any) { + if (abortController.signal.aborted) return; + setTxFetchStatus("error"); + setTxFetchError( + formatReplayRpcError( + err?.message || "Failed to fetch transaction", + selectedNetwork?.name || "this network", + resolveRpcUrl(selectedNetwork.id, selectedNetwork.rpcUrl).mode || "DEFAULT", + ), + ); + } + }, 500); + + return () => { + clearTimeout(timer); + abortController.abort(); + }; + }, [txHash, selectedNetwork, resolveRpcUrl, config, saveConfig]); + + return { txPreview, txFetchStatus, txFetchError, rpcNotice, reset }; +} diff --git a/src/main.tsx b/src/main.tsx index 0e82b2f..4d4ddd2 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -16,20 +16,23 @@ import { RpcAwareWagmiProvider } from './config/rainbowkit'; import { NetworkConfigProvider } from "./contexts/NetworkConfigContext"; +import { LlmConfigProvider } from "./contexts/LlmConfigContext"; createRoot(document.getElementById("root")!).render( - - - - - - - - - + + + + + + + + + + + diff --git a/src/services/DebugBridgeService.ts b/src/services/DebugBridgeService.ts index b5849af..9a60eb4 100644 --- a/src/services/DebugBridgeService.ts +++ b/src/services/DebugBridgeService.ts @@ -524,6 +524,62 @@ class DebugBridgeService { return { snapshots, hasMore }; } + /** + * Fetch snapshot metadata for a sparse set of snapshot IDs. + * More efficient than getSnapshotBatch for sampling across large sessions. + */ + async getSnapshotsBySparseIds( + sessionId: string, + snapshotIds: number[], + ): Promise { + const CONCURRENCY = 25; + const allResults: PromiseSettledResult<{ id: number; value: unknown }>[] = []; + for (let i = 0; i < snapshotIds.length; i += CONCURRENCY) { + const slice = snapshotIds.slice(i, i + CONCURRENCY); + const sliceResults = await Promise.allSettled( + slice.map((id) => + this.rpcCall(sessionId, 'edb_getSnapshotInfo', [id]).then((value) => ({ id, value })), + ), + ); + allResults.push(...sliceResults); + } + + const snapshots: SnapshotListItem[] = allResults + .filter( + (r): r is PromiseFulfilledResult<{ id: number; value: unknown }> => + r.status === 'fulfilled' && r.value !== null, + ) + .map((r) => { + const { id: snapshotId, value } = r.value; + const snap = transformEdbSnapshot(value); + const detail = snap.detail; + + if (snap.type === 'opcode') { + const opcodeDetail = detail as OpcodeSnapshotDetail; + return { + id: snapshotId, + frameId: snap.frameId, + type: 'opcode' as const, + pc: opcodeDetail.pc, + opcodeName: opcodeDetail.opcodeName, + gasRemaining: opcodeDetail.gasRemaining, + }; + } else { + const hookDetail = detail as HookSnapshotDetail; + return { + id: snapshotId, + frameId: snap.frameId, + type: 'hook' as const, + filePath: hookDetail.filePath, + line: hookDetail.line, + functionName: hookDetail.functionName, + }; + } + }); + + return { snapshots, hasMore: false }; + } + /** * Evaluate an expression at a snapshot */ diff --git a/src/services/TxAnalysisStore.ts b/src/services/TxAnalysisStore.ts new file mode 100644 index 0000000..8e89e27 --- /dev/null +++ b/src/services/TxAnalysisStore.ts @@ -0,0 +1,133 @@ +import type { EvidencePacket, Verdict } from "../utils/tx-analysis/types"; + +const DB_NAME = "web3-toolkit-tx-analysis"; +const DB_VERSION = 1; +const STORE_NAME = "analyses"; + +const SENSITIVE_KEYS = new Set([ + "rpcurl", + "rpc_url", + "apikey", + "api_key", + "privatekey", + "private_key", + "secret", + "password", + "authtoken", + "auth_token", +]); + +function normalizeKey(k: string): string { + return k.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]/g, "_").toLowerCase(); +} + +function sanitize(input: T): T { + if (input === null || input === undefined) return input; + if (Array.isArray(input)) return input.map(sanitize) as unknown as T; + if (typeof input !== "object") return input; + const out: Record = {}; + for (const [k, v] of Object.entries(input as Record)) { + if (k === "__proto__" || k === "constructor" || k === "prototype") continue; + if (SENSITIVE_KEYS.has(normalizeKey(k))) continue; + out[k] = sanitize(v as unknown); + } + return out as T; +} + +export type AnalysisDepth = "simple" | "complex"; + +export interface AnalysisRecord { + id: string; + createdAt: number; + depth: AnalysisDepth; + rawPromptHash: string; + packet: EvidencePacket; + verdict: Verdict; +} + +export interface SaveInput { + packet: EvidencePacket; + verdict: Verdict; + depth: AnalysisDepth; + rawPromptHash: string; +} + +export class TxAnalysisStore { + private openDb(): Promise { + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, DB_VERSION); + req.onupgradeneeded = () => { + const db = req.result; + if (!db.objectStoreNames.contains(STORE_NAME)) { + const store = db.createObjectStore(STORE_NAME, { keyPath: "id" }); + store.createIndex("createdAt", "createdAt"); + store.createIndex("simulationId", "packet.simulationId"); + store.createIndex("txHash", "packet.txHash"); + } + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + + async save(input: SaveInput): Promise { + const db = await this.openDb(); + const record: AnalysisRecord = sanitize({ + id: `ana_${crypto.randomUUID()}`, + createdAt: Date.now(), + depth: input.depth, + rawPromptHash: input.rawPromptHash, + packet: input.packet, + verdict: input.verdict, + }); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + tx.objectStore(STORE_NAME).put(record); + tx.oncomplete = () => resolve(record.id); + tx.onerror = () => reject(tx.error); + }); + } + + async get(id: string): Promise { + const db = await this.openDb(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readonly"); + const req = tx.objectStore(STORE_NAME).get(id); + req.onsuccess = () => resolve((req.result as AnalysisRecord) ?? null); + req.onerror = () => reject(req.error); + }); + } + + async list(opts: { limit?: number } = {}): Promise { + const db = await this.openDb(); + const limit = opts.limit ?? 50; + return new Promise((resolve, reject) => { + const results: AnalysisRecord[] = []; + const tx = db.transaction(STORE_NAME, "readonly"); + const idx = tx.objectStore(STORE_NAME).index("createdAt"); + const req = idx.openCursor(null, "prev"); + req.onsuccess = () => { + const cursor = req.result; + if (!cursor || results.length >= limit) { + resolve(results); + return; + } + results.push(cursor.value as AnalysisRecord); + cursor.continue(); + }; + req.onerror = () => reject(req.error); + }); + } + + async delete(id: string): Promise { + const db = await this.openDb(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + tx.objectStore(STORE_NAME).delete(id); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); + } +} + +export const txAnalysisStore = new TxAnalysisStore(); diff --git a/src/types/abi.ts b/src/types/abi.ts index 8b01135..f9e5880 100644 --- a/src/types/abi.ts +++ b/src/types/abi.ts @@ -1,4 +1,8 @@ -import type { ABIFetchResult } from './index'; +export interface ABIFetchResult { + success: boolean; + abi?: string; + error?: string; +} export interface ExtendedABITokenInfo { name?: string; diff --git a/src/types/chain.ts b/src/types/chain.ts new file mode 100644 index 0000000..e6e260e --- /dev/null +++ b/src/types/chain.ts @@ -0,0 +1,22 @@ +export interface ExplorerAPI { + name: string; + url: string; + type: 'etherscan' | 'blockscout'; +} + +export type ExplorerSource = 'sourcify' | 'blockscout' | 'etherscan'; + +export interface Chain { + id: number; + name: string; + rpcUrl: string; + explorerUrl?: string; + blockExplorer?: string; + apiUrl?: string; + explorers?: ExplorerAPI[]; + nativeCurrency: { + name: string; + symbol: string; + decimals: number; + }; +} diff --git a/src/types/contractInfo.ts b/src/types/contractInfo.ts index ef159b3..696612d 100644 --- a/src/types/contractInfo.ts +++ b/src/types/contractInfo.ts @@ -1,4 +1,4 @@ -import type { Chain } from './index'; +import type { Chain } from './chain'; export type ContractSearchStatus = 'searching' | 'found' | 'not_found' | 'error'; @@ -8,7 +8,7 @@ export interface ContractInfoResult { chain: Chain; contractName?: string; abi?: string; - source?: 'sourcify' | 'blockscout' | 'etherscan' | 'blockscout-bytecode' | 'blockscout-ebd' | 'whatsabi'; + source?: 'sourcify' | 'blockscout' | 'etherscan' | 'blockscout-bytecode' | 'whatsabi'; explorerName?: string; verified?: boolean; // Optional tokenType for legacy UI; current detection happens elsewhere diff --git a/src/types/index.ts b/src/types/index.ts index 2068a50..35004f6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,31 +1,12 @@ -export interface ExplorerAPI { - name: string; - url: string; - type: 'etherscan' | 'blockscout'; -} +export type { Chain, ExplorerAPI, ExplorerSource } from './chain'; -export type ExplorerSource = 'sourcify' | 'blockscout' | 'etherscan'; +export type { + ABIFetchResult, + ExtendedABIFetchResult, + ExtendedABITokenInfo, +} from './abi'; -export interface Chain { - id: number; - name: string; - rpcUrl: string; - explorerUrl?: string; - blockExplorer?: string; - apiUrl?: string; - explorers?: ExplorerAPI[]; - nativeCurrency: { - name: string; - symbol: string; - decimals: number; - }; -} - -export interface ABIFetchResult { - success: boolean; - abi?: string; - error?: string; -} +import type { Chain } from './chain'; export interface ContractInfo { address: string; @@ -35,11 +16,4 @@ export interface ContractInfo { verified?: boolean; } -export type { - ContractInfoResult, -} from './contractInfo'; - -export type { - ExtendedABIFetchResult, - ExtendedABITokenInfo, -} from './abi'; +export type { ContractInfoResult } from './contractInfo'; diff --git a/src/utils/addressConstants.ts b/src/utils/addressConstants.ts new file mode 100644 index 0000000..5cbc036 --- /dev/null +++ b/src/utils/addressConstants.ts @@ -0,0 +1,15 @@ +export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; + +export const NATIVE_TOKEN_SENTINEL = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + +export const NATIVE_SENTINELS: ReadonlySet = new Set([ + ZERO_ADDRESS, + NATIVE_TOKEN_SENTINEL, +]); + +export const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"; + +export const isNativeToken = (address: string | null | undefined): boolean => { + if (!address) return false; + return NATIVE_SENTINELS.has(address.toLowerCase()); +}; diff --git a/src/utils/cache/sourcifyCache.ts b/src/utils/cache/sourcifyCache.ts index bdbc60f..79cd0a6 100644 --- a/src/utils/cache/sourcifyCache.ts +++ b/src/utils/cache/sourcifyCache.ts @@ -8,7 +8,6 @@ * - fetchStorageLayout.ts (storage layout + sources) * - resolver/sources/sourcify.ts (ABI + metadata + sources) * - transaction-simulation/artifactFetching.ts (metadata + sources) - * - fetchers/sourcify.ts (legacy fetcher) * * Design: * - Cache is keyed by `${chainId}:${address}:${fieldsKey}` where fieldsKey diff --git a/src/utils/edbTraceConverter.ts b/src/utils/edbTraceConverter.ts index 2aaa002..03c77ff 100644 --- a/src/utils/edbTraceConverter.ts +++ b/src/utils/edbTraceConverter.ts @@ -1,11 +1,8 @@ /** - * EDB trace-to-artifact conversion helpers. - * - * Extracted from simulationArtifacts.ts to keep each module under 800 lines. - */ + * EDB trace-to-artifact conversion helpers. */ import { ethers } from "ethers"; -import { ensureArray } from "./simulationArtifacts"; +import { ensureArray } from "./simulationArtifactTypes"; import type { SimulationCallNode, SimulationEventEntry, diff --git a/src/utils/hack-analysis/classifier.ts b/src/utils/hack-analysis/classifier.ts new file mode 100644 index 0000000..1633080 --- /dev/null +++ b/src/utils/hack-analysis/classifier.ts @@ -0,0 +1,266 @@ +import type { EvidencePacket } from "../tx-analysis/types"; +import type { ClassifierClass } from "./types"; + +export interface ClassifierLabel { + class: ClassifierClass; + confidence: number; + rationale: string; + evidenceIds: string[]; +} + +type Rule = (packet: EvidencePacket) => ClassifierLabel | null; + +// Real EVM selectors (verified). +const FLASHLOAN_SELECTORS = new Set([ + "0xab9c4b5d", // Aave v2: flashLoan(address,address,uint256,bytes) + "0x42b0b77c", // Aave v3: flashLoanSimple(address,address,uint256,bytes,uint16) + "0x5c38449e", // Balancer: flashLoan(address,address[],uint256[],bytes) + "0x5cffe9de", // dYdX / Maker DssFlash: flash(address,uint256,bytes) variants +]); + +const reentrancyRule: Rule = (packet) => { + for (const r of packet.reads) { + if (!r.followsWriteId) continue; + const w = packet.writes.find((x) => x.id === r.followsWriteId); + if (!w) continue; + const interveningCall = packet.triggers.find( + (t) => + (t.kind === "CALL" || t.kind === "DELEGATECALL") && + t.opcodeIndex > w.opcodeIndex && + t.opcodeIndex < r.opcodeIndex && + t.contract.toLowerCase() !== r.contract.toLowerCase(), + ); + if (interveningCall) { + return { + class: "reentrancy", + confidence: 0.8, + rationale: + "SLOAD on a slot that was SSTOREd earlier, with an external CALL to a different contract between write and read.", + evidenceIds: [w.id, r.id, interveningCall.id], + }; + } + } + return null; +}; + +const flashloanRule: Rule = (packet) => { + const loan = packet.triggers.find((t) => t.selector && FLASHLOAN_SELECTORS.has(t.selector.toLowerCase())); + if (!loan) return null; + + const slotCounts = new Map(); + for (const w of packet.writes) { + const k = `${w.contract.toLowerCase()}:${w.slot}`; + slotCounts.set(k, (slotCounts.get(k) ?? 0) + 1); + } + const hasRepeatedWrite = [...slotCounts.values()].some((c) => c >= 3); + if (!hasRepeatedWrite) return null; + + const bigProfit = packet.heuristics.find((h) => h.name === "large_delta"); + if (!bigProfit) return null; + const profitRow = packet.profit.find((p) => p.id === bigProfit.evidenceId); + if (!profitRow || profitRow.direction !== "in" || profitRow.holder.toLowerCase() !== packet.from.toLowerCase()) return null; + + return { + class: "flashloan-price-manipulation", + confidence: 0.8, + rationale: `flashLoan selector ${loan.selector}, pool slot written ≥3× in one tx, attacker netted > 10 ETH equivalent.`, + evidenceIds: [loan.id, profitRow.id], + }; +}; + +const delegatecallRule: Rule = (packet) => { + const delegates = packet.triggers.filter((t) => t.kind === "DELEGATECALL"); + if (delegates.length === 0) return null; + + const knownUnverified = delegates.filter((d) => { + const meta = packet.contracts.find((c) => c.address.toLowerCase() === d.contract.toLowerCase()); + return meta !== undefined && meta.verified === false; + }); + if (knownUnverified.length === 0) return null; + + const slotZeroWrite = packet.writes.find((w) => w.slot === "0x0" || w.slot === "0x00"); + if (!slotZeroWrite) return null; + + return { + class: "delegatecall-to-user-controlled", + confidence: 0.85, + rationale: + "DELEGATECALL to an explicitly-unverified contract combined with a slot-0 write (singleton/impl swap pattern).", + evidenceIds: [...knownUnverified.map((d) => d.id), slotZeroWrite.id], + }; +}; + +const TRANSFER_FROM_SEL = "0x23b872dd"; +const approvalDrainRule: Rule = (packet) => { + const hits: string[] = []; + for (const t of packet.triggers) { + if (t.kind !== "CALL") continue; // no STATICCALL + if (t.selector?.toLowerCase() !== TRANSFER_FROM_SEL) continue; + const fromArg = t.args.find((a) => a.name === "from")?.value?.toString().toLowerCase(); + if (!fromArg) continue; + if (fromArg === packet.from.toLowerCase()) continue; // signer pulling their own tokens is legit + hits.push(t.id); + } + if (hits.length === 0) return null; + + const attackerProfit = packet.profit.find( + (p) => p.direction === "in" && p.holder.toLowerCase() === packet.from.toLowerCase(), + ); + if (!attackerProfit) return null; + + return { + class: "approval-drain", + confidence: 0.75, + rationale: + "transferFrom moved tokens FROM an address that did not sign the tx, TO the tx signer — the fingerprint of a pre-authorized allowance being exercised by an attacker.", + evidenceIds: [...hits, attackerProfit.id], + }; +}; + +// Note: bare "safe" is intentionally excluded — too many unrelated contracts +// (SafeMath-like libs, "SafeVault" wrappers) share that substring. We require +// a disambiguated multisig identifier. +const SAFE_NAMES = new Set(["gnosissafe", "gnosissafeproxy", "safeproxy"]); +const signerCompromiseRule: Rule = (packet) => { + if (!packet.to) return null; + const to = packet.to; + const meta = packet.contracts.find((c) => c.address.toLowerCase() === to.toLowerCase()); + if (!meta?.name || !SAFE_NAMES.has(meta.name.replace(/\s+/g, "").toLowerCase())) return null; + + const delegate = packet.triggers.find((t) => t.kind === "DELEGATECALL"); + if (!delegate) return null; + const slotZero = packet.writes.find((w) => w.slot === "0x0" || w.slot === "0x00"); + if (!slotZero) return null; + + return { + class: "signer-compromise", + confidence: 0.75, + rationale: + "Tx target is a known Safe/multisig, with a delegatecall + slot-0 write — the Bybit-style fingerprint for signers unknowingly approving a singleton swap.", + evidenceIds: [delegate.id, slotZero.id], + }; +}; + +const ORACLE_SELECTORS = new Set([ + "0x50d25bcd", // Chainlink latestAnswer() + "0xfeaf968c", // Chainlink latestRoundData() + "0xb3596f07", // Aave getAssetPrice(address) +]); +const oracleManipulationRule: Rule = (packet) => { + const oracleRead = packet.triggers.find( + (t) => t.kind === "STATICCALL" && t.selector && ORACLE_SELECTORS.has(t.selector.toLowerCase()), + ); + if (!oracleRead) return null; + const flash = packet.triggers.find((t) => t.selector && FLASHLOAN_SELECTORS.has(t.selector.toLowerCase())); + if (!flash) return null; + const attackerProfit = packet.profit.find((p) => p.direction === "in" && p.holder.toLowerCase() === packet.from.toLowerCase()); + if (!attackerProfit) return null; + + return { + class: "oracle-manipulation", + confidence: 0.5, + rationale: + "Flashloan co-occurs with an oracle read and attacker profit — consistent with pool-price manipulation feeding into a dependent oracle.", + evidenceIds: [oracleRead.id, flash.id, attackerProfit.id], + }; +}; + +const BRIDGE_NAME_RE = /bridge|gateway|crosschain/i; +const BRIDGE_FN_RE = /message|vaa|execute|verify|process/i; +const bridgeForgeryRule: Rule = (packet) => { + if (!packet.to) return null; + const to = packet.to; + const meta = packet.contracts.find((c) => c.address.toLowerCase() === to.toLowerCase()); + if (!meta?.name || !BRIDGE_NAME_RE.test(meta.name)) return null; + const bridgeTrigger = packet.triggers.find((t) => t.function && BRIDGE_FN_RE.test(t.function)); + if (!bridgeTrigger) return null; + const attackerProfit = packet.profit.find( + (p) => p.direction === "in" && p.holder.toLowerCase() === packet.from.toLowerCase(), + ); + if (!attackerProfit) return null; + + return { + class: "bridge-message-forgery", + confidence: 0.5, + rationale: + "Call against a known-bridge contract invokes a message/VAA/execute function and the signer netted value — consistent with a forged-inbound-message shape.", + evidenceIds: [bridgeTrigger.id, attackerProfit.id], + }; +}; + +const GOV_FN_RE = /propose|queueTransaction|executeTransaction|setAdmin|setOwner|transferOwnership|emergencyPause|grantRole/i; +const governanceTakeoverRule: Rule = (packet) => { + const govCall = packet.triggers.find((t) => t.function && GOV_FN_RE.test(t.function)); + if (!govCall) return null; + const attackerProfit = packet.profit.find((p) => p.direction === "in" && p.holder.toLowerCase() === packet.from.toLowerCase()); + const hasLarge = packet.heuristics.some((h) => h.name === "large_delta"); + if (!attackerProfit && !hasLarge) return null; + + return { + class: "governance-takeover", + confidence: 0.5, + rationale: "Governance-flavored selector co-occurs with attacker profit or a large value delta.", + evidenceIds: [govCall.id, ...(attackerProfit ? [attackerProfit.id] : [])], + }; +}; + +const accessControlRule: Rule = (packet) => { + const privilegedWrite = packet.writes.find( + (w) => w.label !== null && /owner|admin|role|minter|guardian|governor/i.test(w.label), + ); + if (!privilegedWrite) return null; + const attackerProfit = packet.profit.find((p) => p.direction === "in" && p.holder.toLowerCase() === packet.from.toLowerCase()); + if (!attackerProfit) return null; + + return { + class: "access-control-bypass", + confidence: 0.45, + rationale: "Write to a storage slot labeled as a privilege role, co-occurring with attacker profit.", + evidenceIds: [privilegedWrite.id, attackerProfit.id], + }; +}; + +const MATH_FN_RE = /donate|redeem|liquidate|emergencyWithdraw/i; +const mathInvariantRule: Rule = (packet) => { + const flash = packet.triggers.find((t) => t.selector && FLASHLOAN_SELECTORS.has(t.selector.toLowerCase())); + if (!flash) return null; + const attackerProfit = packet.profit.find((p) => p.direction === "in" && p.holder.toLowerCase() === packet.from.toLowerCase()); + if (!attackerProfit) return null; + const big = packet.heuristics.some((h) => h.name === "large_delta" && h.evidenceId === attackerProfit.id); + if (!big) return null; + const slotCounts = new Map(); + for (const w of packet.writes) { + const k = `${w.contract.toLowerCase()}:${w.slot}`; + slotCounts.set(k, (slotCounts.get(k) ?? 0) + 1); + } + const hasRepeat = [...slotCounts.values()].some((c) => c >= 3); + if (hasRepeat) return null; // let flashloan-price-manipulation own this case + const mathFn = packet.triggers.find((t) => t.function && MATH_FN_RE.test(t.function)); + if (!mathFn) return null; + + return { + class: "math-invariant-manipulation", + confidence: 0.5, + rationale: "Flashloan + big attacker profit + math-flavored selector, but without the repeated-write fingerprint of pool-reserve price manipulation.", + evidenceIds: [flash.id, mathFn.id, attackerProfit.id], + }; +}; + +const RULES: Rule[] = [reentrancyRule, flashloanRule, delegatecallRule, approvalDrainRule, signerCompromiseRule, oracleManipulationRule, bridgeForgeryRule, governanceTakeoverRule, accessControlRule, mathInvariantRule]; + +export function classify(packet: EvidencePacket): ClassifierLabel[] { + const hits = RULES.map((r) => r(packet)).filter( + (x): x is ClassifierLabel => x !== null, + ); + if (hits.length === 0) { + return [ + { + class: "unknown", + confidence: 0.5, + rationale: "No rule matched.", + evidenceIds: [], + }, + ]; + } + return hits; +} diff --git a/src/utils/hack-analysis/fixtures/access-control-fixture.ts b/src/utils/hack-analysis/fixtures/access-control-fixture.ts new file mode 100644 index 0000000..f2404c4 --- /dev/null +++ b/src/utils/hack-analysis/fixtures/access-control-fixture.ts @@ -0,0 +1,27 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const ATTACKER = "0x" + "a".repeat(40); +const VICTIM = "0x" + "c".repeat(40); + +export const accessControlFixture: EvidencePacket = { + txHash: "0x" + "9".repeat(64), + simulationId: "sim-access", + chainId: 1, + from: ATTACKER, + to: VICTIM, + success: true, + revertReason: null, + writes: [ + { id: "w_0", contract: VICTIM, slot: "0x3", valueBefore: "0x0", valueAfter: "0x" + "a".repeat(40).padStart(64, "0"), label: "owner", typeHint: null, opcodeIndex: 10, sourceLine: null, sourceFile: null }, + ], + reads: [], + triggers: [ + { id: "t_0", contract: VICTIM, kind: "CALL", selector: "0xdead0002", function: "unknownFunction()", args: [], logTopics: [], opcodeIndex: 5 }, + ], + profit: [ + { id: "p_0", token: null, asset: "ETH", holder: ATTACKER, delta: "20000000000000000000", direction: "in", opcodeIndex: 20 }, + ], + contracts: [], + heuristics: [], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; diff --git a/src/utils/hack-analysis/fixtures/approval-drain-fixture.ts b/src/utils/hack-analysis/fixtures/approval-drain-fixture.ts new file mode 100644 index 0000000..3df86d5 --- /dev/null +++ b/src/utils/hack-analysis/fixtures/approval-drain-fixture.ts @@ -0,0 +1,102 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const VICTIM_EOA = "0x" + "a".repeat(40); +const ATTACKER = "0x" + "b".repeat(40); +const TOKEN = "0x" + "c".repeat(40); + +/** Attacker signs the tx; uses pre-existing allowance of VICTIM_EOA to pull tokens. */ +export const approvalDrainFixture: EvidencePacket = { + txHash: "0x" + "5".repeat(64), + simulationId: "sim-approval-drain", + chainId: 1, + from: ATTACKER, + to: TOKEN, + success: true, + revertReason: null, + writes: [], + reads: [], + triggers: [ + { + id: "t_0", + contract: TOKEN, + kind: "CALL", + selector: "0x23b872dd", + function: "transferFrom(address,address,uint256)", + args: [ + { name: "from", value: VICTIM_EOA }, + { name: "to", value: ATTACKER }, + { name: "amount", value: "1000000000000000000000" }, + ], + logTopics: [], + opcodeIndex: 5, + }, + ], + profit: [ + { + id: "p_0", + token: TOKEN, + asset: "ERC20", + holder: ATTACKER, + delta: "1000000000000000000000", + direction: "in", + opcodeIndex: 8, + }, + { + id: "p_1", + token: TOKEN, + asset: "ERC20", + holder: VICTIM_EOA, + delta: "-1000000000000000000000", + direction: "out", + opcodeIndex: 8, + }, + ], + contracts: [], + heuristics: [{ name: "large_delta", evidenceId: "p_0", reason: "> 10 ETH equiv" }], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; + +/** Legit Uniswap router swap: VICTIM_EOA (=signer) approves router, router does transferFrom(me, router, X). Rule MUST NOT fire. */ +const SIGNER = VICTIM_EOA; +const ROUTER = "0x" + "d".repeat(40); +export const routerSwapFixture: EvidencePacket = { + ...approvalDrainFixture, + from: SIGNER, + to: ROUTER, + triggers: [ + { + id: "t_0", + contract: TOKEN, + kind: "CALL", + selector: "0x23b872dd", + function: "transferFrom(address,address,uint256)", + args: [ + { name: "from", value: SIGNER }, + { name: "to", value: ROUTER }, + { name: "amount", value: "1000000000000000000000" }, + ], + logTopics: [], + opcodeIndex: 5, + }, + ], + profit: [ + { + id: "p_0", + token: TOKEN, + asset: "ERC20", + holder: ROUTER, + delta: "1000000000000000000000", + direction: "in", + opcodeIndex: 8, + }, + { + id: "p_1", + token: TOKEN, + asset: "ERC20", + holder: SIGNER, + delta: "-1000000000000000000000", + direction: "out", + opcodeIndex: 8, + }, + ], +}; diff --git a/src/utils/hack-analysis/fixtures/benign-vault-deposit.ts b/src/utils/hack-analysis/fixtures/benign-vault-deposit.ts new file mode 100644 index 0000000..71451cf --- /dev/null +++ b/src/utils/hack-analysis/fixtures/benign-vault-deposit.ts @@ -0,0 +1,45 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const A = "0x" + "a".repeat(40); +const B = "0x" + "b".repeat(40); + +export const benignVaultDeposit: EvidencePacket = { + txHash: "0x" + "1".repeat(64), + simulationId: "sim-benign", + chainId: 1, + from: A, + to: B, + success: true, + revertReason: null, + writes: [ + { + id: "w_0", + contract: B, + slot: "0x1", + valueBefore: "0x0", + valueAfter: "0x1", + label: null, + typeHint: null, + opcodeIndex: 10, + sourceLine: null, + sourceFile: null, + }, + ], + reads: [], + triggers: [ + { + id: "t_0", + contract: B, + kind: "CALL", + selector: "0x6e553f65", + function: "deposit(uint256,address)", + args: [], + logTopics: [], + opcodeIndex: 5, + }, + ], + profit: [], + contracts: [], + heuristics: [], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; diff --git a/src/utils/hack-analysis/fixtures/bridge-forgery-fixture.ts b/src/utils/hack-analysis/fixtures/bridge-forgery-fixture.ts new file mode 100644 index 0000000..4185e33 --- /dev/null +++ b/src/utils/hack-analysis/fixtures/bridge-forgery-fixture.ts @@ -0,0 +1,35 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const ATTACKER = "0x" + "a".repeat(40); +const BRIDGE = "0x" + "b".repeat(40); + +export const bridgeForgeryFixture: EvidencePacket = { + txHash: "0x" + "7".repeat(64), + simulationId: "sim-bridge-forgery", + chainId: 1, + from: ATTACKER, + to: BRIDGE, + success: true, + revertReason: null, + writes: [], + reads: [], + triggers: [ + { id: "t_0", contract: BRIDGE, kind: "CALL", selector: "0xdeadbeef", function: "processMessage(bytes,bytes)", args: [], logTopics: [], opcodeIndex: 5 }, + ], + profit: [ + { id: "p_0", token: null, asset: "ETH", holder: ATTACKER, delta: "40000000000000000000", direction: "in", opcodeIndex: 20 }, + ], + contracts: [ + { address: BRIDGE, name: "L1CrossChainBridge", proxyImplementation: null, verified: true, sourceProvider: "etherscan" }, + ], + heuristics: [], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; + +/** Legit router swap: contract name does NOT match bridge regex. Rule MUST NOT fire. */ +export const normalRouterFixture: EvidencePacket = { + ...bridgeForgeryFixture, + contracts: [ + { address: BRIDGE, name: "UniswapV3Router", proxyImplementation: null, verified: true, sourceProvider: "etherscan" }, + ], +}; diff --git a/src/utils/hack-analysis/fixtures/delegatecall-fixture.ts b/src/utils/hack-analysis/fixtures/delegatecall-fixture.ts new file mode 100644 index 0000000..7d2106d --- /dev/null +++ b/src/utils/hack-analysis/fixtures/delegatecall-fixture.ts @@ -0,0 +1,82 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const VICTIM = "0x" + "1".repeat(40); +const ATTACKER_IMPL = "0x" + "e".repeat(40); + +export const delegatecallFixture: EvidencePacket = { + txHash: "0x" + "4".repeat(64), + simulationId: "sim-delegate", + chainId: 1, + from: "0x" + "a".repeat(40), + to: VICTIM, + success: true, + revertReason: null, + writes: [ + { + id: "w_0", + contract: VICTIM, + slot: "0x0", + valueBefore: "0x0", + valueAfter: "0x" + "e".repeat(40).padStart(64, "0"), + label: "singleton", + typeHint: null, + opcodeIndex: 30, + sourceLine: null, + sourceFile: null, + }, + ], + reads: [], + triggers: [ + { + id: "t_0", + contract: ATTACKER_IMPL, + kind: "DELEGATECALL", + selector: "0xa9059cbb", + function: "transfer(address,uint256)", + args: [], + logTopics: [], + opcodeIndex: 20, + }, + ], + profit: [], + contracts: [ + { + address: VICTIM, + name: "Safe", + proxyImplementation: "0x" + "1".repeat(40), + verified: true, + sourceProvider: "etherscan", + }, + { + address: ATTACKER_IMPL, + name: null, + proxyImplementation: null, + verified: false, + sourceProvider: "etherscan", + }, + ], + heuristics: [], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; + +/** Benign proxy call — same delegatecall, target is verified, no slot-0 write. Rule must NOT fire. */ +export const benignProxyCallFixture: EvidencePacket = { + ...delegatecallFixture, + writes: [], + contracts: [ + { + address: VICTIM, + name: "Safe", + proxyImplementation: "0x" + "1".repeat(40), + verified: true, + sourceProvider: "etherscan", + }, + { + address: ATTACKER_IMPL, + name: "SafeSingleton", + proxyImplementation: null, + verified: true, + sourceProvider: "etherscan", + }, + ], +}; diff --git a/src/utils/hack-analysis/fixtures/flashloan-fixture.ts b/src/utils/hack-analysis/fixtures/flashloan-fixture.ts new file mode 100644 index 0000000..4721fce --- /dev/null +++ b/src/utils/hack-analysis/fixtures/flashloan-fixture.ts @@ -0,0 +1,33 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; +const ATTACKER = "0x" + "a".repeat(40); +const POOL = "0x" + "c".repeat(40); +const LENDER = "0x" + "b".repeat(40); + +export const flashloanFixture: EvidencePacket = { + txHash: "0x" + "3".repeat(64), + simulationId: "sim-flashloan", + chainId: 1, + from: ATTACKER, + to: LENDER, + success: true, + revertReason: null, + writes: Array.from({ length: 4 }, (_, i) => ({ + id: `w_${i}`, contract: POOL, slot: "0x5", valueBefore: "0x0", valueAfter: "0x100", + label: "reserve", typeHint: null, opcodeIndex: 20 + i, sourceLine: null, sourceFile: null, + })), + reads: [], + triggers: [ + { id: "t_0", contract: LENDER, kind: "CALL", selector: "0xab9c4b5d", function: "flashLoan(address,address,uint256,bytes)", args: [], logTopics: [], opcodeIndex: 5 }, + { id: "t_1", contract: POOL, kind: "CALL", selector: "0x022c0d9f", function: "swap(uint256,uint256,address,bytes)", args: [], logTopics: [], opcodeIndex: 25 }, + { id: "t_2", contract: LENDER, kind: "CALL", selector: "0xa9059cbb", function: "transfer(address,uint256)", args: [], logTopics: [], opcodeIndex: 40 }, + ], + profit: [ + { id: "p_0", token: null, asset: "ETH", holder: ATTACKER, delta: "50000000000000000000", direction: "in", opcodeIndex: 45 }, + ], + contracts: [], + heuristics: [ + { name: "large_delta", evidenceId: "p_0", reason: "attacker profit exceeds 10 ETH" }, + { name: "accumulator", evidenceId: "w_0", reason: "POOL slot 0x5 written 4 times" }, + ], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; diff --git a/src/utils/hack-analysis/fixtures/governance-takeover-fixture.ts b/src/utils/hack-analysis/fixtures/governance-takeover-fixture.ts new file mode 100644 index 0000000..896692a --- /dev/null +++ b/src/utils/hack-analysis/fixtures/governance-takeover-fixture.ts @@ -0,0 +1,25 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const ATTACKER = "0x" + "a".repeat(40); +const GOV = "0x" + "9".repeat(40); + +export const governanceTakeoverFixture: EvidencePacket = { + txHash: "0x" + "8".repeat(64), + simulationId: "sim-gov", + chainId: 1, + from: ATTACKER, + to: GOV, + success: true, + revertReason: null, + writes: [], + reads: [], + triggers: [ + { id: "t_0", contract: GOV, kind: "CALL", selector: "0xdead0001", function: "executeTransaction(address,uint256,string,bytes,uint256)", args: [], logTopics: [], opcodeIndex: 5 }, + ], + profit: [ + { id: "p_0", token: null, asset: "ETH", holder: ATTACKER, delta: "25000000000000000000", direction: "in", opcodeIndex: 20 }, + ], + contracts: [], + heuristics: [], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; diff --git a/src/utils/hack-analysis/fixtures/math-invariant-fixture.ts b/src/utils/hack-analysis/fixtures/math-invariant-fixture.ts new file mode 100644 index 0000000..923f3d4 --- /dev/null +++ b/src/utils/hack-analysis/fixtures/math-invariant-fixture.ts @@ -0,0 +1,33 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const ATTACKER = "0x" + "a".repeat(40); +const LENDER = "0x" + "b".repeat(40); +const POOL = "0x" + "c".repeat(40); + +export const mathInvariantFixture: EvidencePacket = { + txHash: "0x" + "a".repeat(64), + simulationId: "sim-math", + chainId: 1, + from: ATTACKER, + to: LENDER, + success: true, + revertReason: null, + writes: [ + // Only ONE write per slot — no repeated same-slot writes (that would be flashloan-price-manipulation) + { id: "w_0", contract: POOL, slot: "0x2", valueBefore: "0x0", valueAfter: "0x1", label: null, typeHint: null, opcodeIndex: 10, sourceLine: null, sourceFile: null }, + { id: "w_1", contract: POOL, slot: "0x3", valueBefore: "0x0", valueAfter: "0x1", label: null, typeHint: null, opcodeIndex: 12, sourceLine: null, sourceFile: null }, + ], + reads: [], + triggers: [ + { id: "t_0", contract: LENDER, kind: "CALL", selector: "0xab9c4b5d", function: "flashLoan(address,address,uint256,bytes)", args: [], logTopics: [], opcodeIndex: 5 }, + { id: "t_1", contract: POOL, kind: "CALL", selector: "0xdead0003", function: "donate(uint256)", args: [], logTopics: [], opcodeIndex: 15 }, + ], + profit: [ + { id: "p_0", token: null, asset: "ETH", holder: ATTACKER, delta: "60000000000000000000", direction: "in", opcodeIndex: 20 }, + ], + contracts: [], + heuristics: [ + { name: "large_delta", evidenceId: "p_0", reason: "attacker netted > 10 ETH" }, + ], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; diff --git a/src/utils/hack-analysis/fixtures/oracle-manipulation-fixture.ts b/src/utils/hack-analysis/fixtures/oracle-manipulation-fixture.ts new file mode 100644 index 0000000..4467242 --- /dev/null +++ b/src/utils/hack-analysis/fixtures/oracle-manipulation-fixture.ts @@ -0,0 +1,27 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const ATTACKER = "0x" + "a".repeat(40); +const LENDER = "0x" + "b".repeat(40); +const ORACLE = "0x" + "f".repeat(40); + +export const oracleManipulationFixture: EvidencePacket = { + txHash: "0x" + "6".repeat(64), + simulationId: "sim-oracle", + chainId: 1, + from: ATTACKER, + to: LENDER, + success: true, + revertReason: null, + writes: [], + reads: [], + triggers: [ + { id: "t_0", contract: LENDER, kind: "CALL", selector: "0xab9c4b5d", function: "flashLoan(address,address,uint256,bytes)", args: [], logTopics: [], opcodeIndex: 5 }, + { id: "t_1", contract: ORACLE, kind: "STATICCALL", selector: "0x50d25bcd", function: "latestAnswer()", args: [], logTopics: [], opcodeIndex: 15 }, + ], + profit: [ + { id: "p_0", token: null, asset: "ETH", holder: ATTACKER, delta: "30000000000000000000", direction: "in", opcodeIndex: 20 }, + ], + contracts: [], + heuristics: [], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; diff --git a/src/utils/hack-analysis/fixtures/reentrancy-fixture.ts b/src/utils/hack-analysis/fixtures/reentrancy-fixture.ts new file mode 100644 index 0000000..bad4fbb --- /dev/null +++ b/src/utils/hack-analysis/fixtures/reentrancy-fixture.ts @@ -0,0 +1,94 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +const VICTIM = "0x" + "1".repeat(40); +const ATTACKER = "0x" + "a".repeat(40); + +export const reentrancyFixture: EvidencePacket = { + txHash: "0x" + "2".repeat(64), + simulationId: "sim-reentry", + chainId: 1, + from: ATTACKER, + to: VICTIM, + success: true, + revertReason: null, + writes: [ + { + id: "w_0", + contract: VICTIM, + slot: "0x1", + valueBefore: "0x64", + valueAfter: "0x0", + label: "balance", + typeHint: null, + opcodeIndex: 15, + sourceLine: null, + sourceFile: null, + }, + ], + reads: [ + { + id: "r_0", + contract: VICTIM, + slot: "0x1", + value: "0x64", + label: null, + opcodeIndex: 10, + sourceLine: null, + sourceFile: null, + followsWriteId: null, + }, + { + id: "r_1", + contract: VICTIM, + slot: "0x1", + value: "0x64", + label: null, + opcodeIndex: 22, + sourceLine: null, + sourceFile: null, + followsWriteId: "w_0", + }, + ], + triggers: [ + { + id: "t_0", + contract: VICTIM, + kind: "CALL", + selector: "0x2e1a7d4d", + function: "withdraw(uint256)", + args: [], + logTopics: [], + opcodeIndex: 5, + }, + { + id: "t_1", + contract: ATTACKER, + kind: "CALL", + selector: "0x", + function: null, + args: [], + logTopics: [], + opcodeIndex: 18, + }, + ], + profit: [ + { + id: "p_0", + token: null, + asset: "ETH", + holder: ATTACKER, + delta: "200000000000000000000", + direction: "in", + opcodeIndex: 28, + }, + ], + contracts: [], + heuristics: [ + { + name: "sload_after_sstore", + evidenceId: "r_1", + reason: "classic reentry re-read", + }, + ], + truncated: { writes: false, reads: false, triggers: false, profit: false }, +}; diff --git a/src/utils/hack-analysis/fixtures/signer-compromise-fixture.ts b/src/utils/hack-analysis/fixtures/signer-compromise-fixture.ts new file mode 100644 index 0000000..83bf1fb --- /dev/null +++ b/src/utils/hack-analysis/fixtures/signer-compromise-fixture.ts @@ -0,0 +1,22 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; +import { delegatecallFixture } from "./delegatecall-fixture"; + +export const signerCompromiseFixture: EvidencePacket = { + ...delegatecallFixture, + contracts: [ + { + address: delegatecallFixture.to!, + name: "GnosisSafe", + proxyImplementation: "0x" + "1".repeat(40), + verified: true, + sourceProvider: "etherscan", + }, + { + address: "0x" + "e".repeat(40), + name: null, + proxyImplementation: null, + verified: false, + sourceProvider: "etherscan", + }, + ], +}; diff --git a/src/utils/hack-analysis/incidents/README.md b/src/utils/hack-analysis/incidents/README.md new file mode 100644 index 0000000..2dada5c --- /dev/null +++ b/src/utils/hack-analysis/incidents/README.md @@ -0,0 +1,52 @@ +# Hack incident library + +Curated EVM post-mortems used by the hack-analysis classifier and retrieval layer. Each incident is a JSON file conforming to the `Incident` schema in [`../types.ts`](../types.ts). + +**Coverage:** 21 incidents, 2022-01 to 2025-02, roughly $3.36B cumulative loss. + +## Incidents + +| Incident | Date | Loss | Exploit classes | +|---|---|---:|---| +| [Qubit QBridge](qubit-2022-01.json) | 2022-01-27 | $80M | bridge-message-forgery, access-control-bypass | +| [Ronin bridge](ronin-2022-03.json) | 2022-03-23 | $624M | signer-compromise | +| [Inverse Finance Anchor](inverse-finance-2022-04.json) | 2022-04-02 | $15.6M | oracle-manipulation, flashloan-price-manipulation | +| [Beanstalk governance](beanstalk-2022-04.json) | 2022-04-17 | $182M | governance-takeover, flashloan-price-manipulation | +| [Rari Fuse pools](rari-fuse-2022-04.json) | 2022-04-30 | $80M | reentrancy | +| [Harmony Horizon](harmony-2022-06.json) | 2022-06-23 | $100M | signer-compromise, bridge-message-forgery | +| [Nomad bridge](nomad-2022-08.json) | 2022-08-01 | $190M | bridge-message-forgery, access-control-bypass | +| [Team Finance](team-finance-2022-10.json) | 2022-10-27 | $14.5M | access-control-bypass, math-invariant-manipulation | +| [Ankr aBNBc](ankr-2022-12.json) | 2022-12-02 | $5M | access-control-bypass | +| [Orion Protocol](orion-protocol-2023-02.json) | 2023-02-02 | $2.9M | reentrancy | +| [Platypus Finance](platypus-2023-02.json) | 2023-02-16 | $8.5M | math-invariant-manipulation, flashloan-price-manipulation | +| [Euler Finance](euler-2023-03.json) | 2023-03-13 | $197M | math-invariant-manipulation, flashloan-price-manipulation | +| [Multichain MPC](multichain-2023-07.json) | 2023-07-06 | $125M | signer-compromise | +| [Curve Vyper reentrancy](curve-vyper-2023-07.json) | 2023-07-30 | $52M | reentrancy | +| [KyberSwap Elastic](kyberswap-elastic-2023-11.json) | 2023-11-22 | $48M | math-invariant-manipulation | +| [Ledger Connect Kit](ledger-connect-2023-12.json) | 2023-12-14 | $610K | approval-drain | +| [Orbit bridge](orbit-bridge-2024-01.json) | 2024-01-01 | $81.5M | signer-compromise, bridge-message-forgery | +| [WOOFi sPMM](woofi-2024-03.json) | 2024-03-05 | $8.75M | oracle-manipulation, flashloan-price-manipulation | +| [Penpie Finance](penpie-2024-09.json) | 2024-09-03 | $27M | reentrancy, math-invariant-manipulation | +| [Radiant multisig](radiant-2024-10.json) | 2024-10-16 | $58M | signer-compromise, delegatecall-to-user-controlled | +| [Bybit cold wallet](bybit-2025-02.json) | 2025-02-21 | $1.46B | signer-compromise, delegatecall-to-user-controlled | + +## What each incident carries + +- `id`, `name`, `chain`, `date`, `protocol`, `amountUsd` +- `canonicalTxs`: on-chain transaction hashes for the exploit +- `exploitClasses`: canonical class labels matching the classifier +- `coreContradiction`: one-line invariant the exploit violated +- `attackSteps`: ordered steps with evidence citations +- `entities`: attacker EOAs, victim contracts, oracles, tokens (with addresses and roles) +- `fundFlow`: token transfers tying the step chain to dollar loss +- `sources`: rekt.news, CertiK, explorer links backing every claim + +See [`../types.ts`](../types.ts) for the full Zod schema. + +## Adding an incident + +1. Create `.json` in this directory matching the schema above. +2. Filename (without `.json`) must equal the `id` field. +3. Addresses must be EIP-55 checksummed. +4. Every `attackStep.sourceIds` entry must reference an `id` in `sources`. +5. Run `npm test -- hack-analysis/incidents` to validate. diff --git a/src/utils/hack-analysis/incidents/ankr-2022-12.json b/src/utils/hack-analysis/incidents/ankr-2022-12.json new file mode 100644 index 0000000..dd7fc11 --- /dev/null +++ b/src/utils/hack-analysis/incidents/ankr-2022-12.json @@ -0,0 +1,121 @@ +{ + "id": "ankr-2022-12", + "name": "Ankr aBNBc Infinite Mint", + "chain": "bsc", + "date": "2022-12-02", + "protocol": "Ankr", + "amountUsd": 5000000, + "canonicalTxs": [ + "0xcbc5ff4a6c9a66274f9bde424777c3dc862ab576e282fbea3c9c2609ca3e282b", + "0xe367d05e7ff37eb6d0b7d763495f218740c979348d7a3b6d8e72d3b947c86e33" + ], + "exploitClasses": ["access-control-bypass"], + "tldr": "A former Ankr developer's retained deployer private key was used to upgrade the aBNBc token contract to a malicious implementation, enabling the attacker to mint 10 trillion tokens out of thin air and drain ~$5M from DEX liquidity pools.", + "coreContradiction": "The upgradeable token's owner check was bypassed not through a code flaw but through legitimate admin authority — the deployer key was real, making the unauthorized upgrade indistinguishable from a routine contract update.", + "attackSteps": [ + { + "order": 1, + "label": "Deployer key compromised", + "detail": "A former Ankr developer retained the deployer private key (0x2Ffc59d32A524611Bb891cab759112A51f9e33C0); the attacker obtained it, likely via supply-chain or phishing, giving full admin control over the upgradeable aBNBc proxy.", + "sourceIds": ["rekt-ankr", "ankr-official"] + }, + { + "order": 2, + "label": "Malicious implementation deployed", + "detail": "The attacker deployed a new aBNBc implementation contract containing an extra function (selector 0x3b3a5522) that mints tokens to any caller with no authorization checks.", + "sourceIds": ["rekt-ankr", "quillaudits-ankr"] + }, + { + "order": 3, + "label": "Proxy upgraded to malicious impl", + "detail": "From the compromised deployer address, the attacker called upgrade() on the proxy admin (0x1bD5dF997c8612652886723406131F582ab93DEf), pointing the aBNBc proxy at the malicious implementation — all at block timestamp Dec-02-2022 12:43:09 UTC.", + "sourceIds": ["rekt-ankr", "quillaudits-ankr"] + }, + { + "order": 4, + "label": "10 trillion aBNBc minted", + "detail": "Nine seconds later the attacker called the backdoor mint function, minting 10,000,000,000,000 aBNBc directly to their wallet (0xf3a465C9fA6663fF50794C698F600Faa4b05c777).", + "sourceIds": ["rekt-ankr", "neptune-ankr"] + }, + { + "order": 5, + "label": "Dumped through PancakeSwap & Helio", + "detail": "The attacker swapped 20 trillion aBNBc for BNB via PancakeSwap, collapsing the token price 99.5% and extracting ~$5M. A secondary actor exploited Helio's stale aBNBc oracle for an additional ~$15.5M.", + "sourceIds": ["rekt-ankr", "neptune-ankr"] + }, + { + "order": 6, + "label": "Proceeds bridged and laundered", + "detail": "BNB and USDC proceeds were bridged to Ethereum via Celer cBridge and routed through Tornado Cash to obscure the trail.", + "sourceIds": ["neptune-ankr", "quillaudits-ankr"] + } + ], + "entities": [ + { + "id": "attacker-eoa", + "role": "attacker-eoa", + "address": "0xf3a465C9fA6663fF50794C698F600Faa4b05c777", + "label": "Ankr Exploiter EOA" + }, + { + "id": "compromised-deployer", + "role": "signer", + "address": "0x2Ffc59d32A524611Bb891cab759112A51f9e33C0", + "label": "Ankr Compromised Deployer" + }, + { + "id": "victim-abnbc", + "role": "token", + "address": "0xE85aFCcDaFBE7F2B096f268e31ccE3da8dA2990A", + "label": "aBNBc Token Proxy" + }, + { + "id": "proxy-admin", + "role": "proxy", + "address": "0x1bD5dF997c8612652886723406131F582ab93DEf", + "label": "aBNBc Proxy Admin" + } + ], + "fundFlow": [ + { + "fromEntityId": "victim-abnbc", + "toEntityId": "attacker-eoa", + "tokenSymbol": "aBNBc", + "amountHuman": "10,000,000,000,000 aBNBc", + "note": "Minted from zero address via backdoor function" + }, + { + "fromEntityId": "attacker-eoa", + "toEntityId": "attacker-eoa", + "tokenSymbol": "USDC", + "amountHuman": "~5,000,000 USD", + "note": "aBNBc swapped for BNB/USDC via PancakeSwap, then bridged to Ethereum" + } + ], + "sources": [ + { + "id": "rekt-ankr", + "kind": "post-mortem", + "url": "https://rekt.news/ankr-helio-rekt", + "publisher": "rekt.news" + }, + { + "id": "ankr-official", + "kind": "official", + "url": "https://www.ankr.com/blog/the-abnbc-token-report/", + "publisher": "Ankr" + }, + { + "id": "quillaudits-ankr", + "kind": "forensic-thread", + "url": "https://quillaudits.medium.com/ankr-protocol-exploit-analysis-quillaudits-553cd4c8d17c", + "publisher": "QuillAudits" + }, + { + "id": "neptune-ankr", + "kind": "forensic-thread", + "url": "https://medium.com/neptune-mutual/taking-a-closer-look-at-ankr-hack-7c6f57ee8d25", + "publisher": "Neptune Mutual" + } + ] +} diff --git a/src/utils/hack-analysis/incidents/beanstalk-2022-04.json b/src/utils/hack-analysis/incidents/beanstalk-2022-04.json new file mode 100644 index 0000000..b25d983 --- /dev/null +++ b/src/utils/hack-analysis/incidents/beanstalk-2022-04.json @@ -0,0 +1,118 @@ +{ + "id": "beanstalk-2022-04", + "name": "Beanstalk Governance Flash Loan Attack", + "chain": "ethereum", + "date": "2022-04-17", + "protocol": "Beanstalk Farms", + "amountUsd": 182000000, + "canonicalTxs": ["0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7"], + "exploitClasses": ["governance-takeover", "flashloan-price-manipulation"], + "tldr": "Attacker flash-borrowed ~$1B in stablecoins from Aave/Uniswap/SushiSwap to acquire 79% of Beanstalk's Stalk voting power in a single block, then called emergencyCommit on a pre-planted malicious BIP that transferred all protocol assets (~$182M) to the attacker contract.", + "coreContradiction": "Beanstalk's governance counted flash-borrowed liquidity-pool deposits as legitimate long-term voting power, so temporary capital worth $1B could irrevocably commit a protocol-draining proposal in the same transaction.", + "attackSteps": [ + { + "order": 1, + "label": "Plant malicious BIPs", + "detail": "A day before the exploit, the attacker created two governance proposals: BIP-18 (drain all funds to attacker) and BIP-19 (donate $250k BEAN to Ukraine as distraction).", + "sourceIds": ["rekt-beanstalk", "immunefi-beanstalk"] + }, + { + "order": 2, + "label": "Flash-borrow ~$1B", + "detail": "In a single transaction the attacker borrowed 350M DAI, 500M USDC, and 150M USDT from Aave, 32M BEAN from Uniswap V2, and 11.6M LUSD from SushiSwap.", + "sourceIds": ["immunefi-beanstalk", "certik-beanstalk"] + }, + { + "order": 3, + "label": "Convert to BEAN3CRV-f LP tokens", + "detail": "Deposited stablecoins into Curve 3pool for 3Crv, then deposited 3Crv into the BEAN3CRV-f Curve pool, obtaining BEAN3CRV-f LP tokens.", + "sourceIds": ["immunefi-beanstalk"] + }, + { + "order": 4, + "label": "Deposit into Beanstalk (acquire Stalk)", + "detail": "Deposited all BEAN3CRV-f tokens into the Beanstalk Diamond, which automatically minted Stalk voting power via LibSilo.incrementBipRoots, giving the attacker ~79% of total Stalk.", + "sourceIds": ["immunefi-beanstalk", "rekt-beanstalk"] + }, + { + "order": 5, + "label": "emergencyCommit BIP-18", + "detail": "Called GovernanceFacet.emergencyCommit(18), which required only ≥67% Stalk approval; the malicious BIP used delegatecall to transfer all BEAN3CRV-f held by the Beanstalk Diamond to the attacker contract.", + "sourceIds": ["immunefi-beanstalk", "certik-beanstalk"] + }, + { + "order": 6, + "label": "Unwind and repay flashloans", + "detail": "Converted BEAN3CRV-f back through Curve into stablecoins, repaid all flash loans with fees, and retained ~24,830 ETH (~$76M) which was bridged to Tornado Cash.", + "sourceIds": ["rekt-beanstalk", "certik-beanstalk"] + } + ], + "entities": [ + { + "id": "attacker-eoa", + "role": "attacker-eoa", + "address": "0x1c5dCdd006EA78a7E4783f9e6021C32935a10fb4", + "label": "Attacker EOA" + }, + { + "id": "attacker-contract", + "role": "attacker-contract", + "address": "0x79224bC0bf70EC34F0ef56ed8251619499a59dEf", + "label": "Attacker exploit contract" + }, + { + "id": "beanstalk-diamond", + "role": "victim-contract", + "address": "0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5", + "label": "Beanstalk Diamond proxy" + }, + { + "id": "governance-facet", + "role": "implementation", + "address": "0xf480eE81a54E21Be47aa02D0F9E29985Bc7667c4", + "label": "GovernanceFacet (emergencyCommit)" + }, + { + "id": "malicious-bip", + "role": "attacker-contract", + "address": "0xE5eCF73603D98A0128F05ed30506ac7A663dBb69", + "label": "Malicious BIP-18 proposal contract" + } + ], + "fundFlow": [ + { + "fromEntityId": "beanstalk-diamond", + "toEntityId": "attacker-contract", + "tokenSymbol": "BEAN3CRV-f", + "amountHuman": "~$182M in protocol assets", + "note": "All protocol funds transferred via emergencyCommit delegatecall in BIP-18" + }, + { + "fromEntityId": "attacker-contract", + "toEntityId": "attacker-eoa", + "tokenSymbol": "ETH", + "amountHuman": "24,830 ETH (~$76M net profit)", + "note": "After repaying $1B flashloans; proceeds sent to Tornado Cash" + } + ], + "sources": [ + { + "id": "rekt-beanstalk", + "kind": "post-mortem", + "url": "https://rekt.news/beanstalk-rekt/", + "publisher": "Rekt News" + }, + { + "id": "immunefi-beanstalk", + "kind": "forensic-thread", + "url": "https://medium.com/immunefi/hack-analysis-beanstalk-governance-attack-april-2022-f42788fc821e", + "publisher": "Immunefi" + }, + { + "id": "certik-beanstalk", + "kind": "forensic-thread", + "url": "https://www.certik.com/resources/blog/6HaLMGIL5sI2fpfEZc0nzS-revisiting-beanstalk-farms-exploit", + "publisher": "CertiK" + } + ] +} diff --git a/src/utils/hack-analysis/incidents/bybit-2025-02.json b/src/utils/hack-analysis/incidents/bybit-2025-02.json new file mode 100644 index 0000000..a6cdfd5 --- /dev/null +++ b/src/utils/hack-analysis/incidents/bybit-2025-02.json @@ -0,0 +1,32 @@ +{ + "id": "bybit-2025-02", + "name": "Bybit Cold Wallet Drain", + "chain": "ethereum", + "date": "2025-02-21", + "protocol": "Bybit", + "amountUsd": 1460000000, + "canonicalTxs": ["0x46deef0f52e3a983b67abf4714448a41dd7ffd6d32d32da69d62081c68ad7882"], + "exploitClasses": ["signer-compromise", "delegatecall-to-user-controlled"], + "tldr": "Bybit lost ~$1.46B when signers unknowingly approved a delegatecall that swapped the Safe's singleton to attacker-controlled code.", + "coreContradiction": "The Safe UI showed a routine ERC20 transfer while the calldata actually invoked a delegatecall rewriting the masterCopy slot.", + "attackSteps": [ + { "order": 1, "label": "Malicious JS injected", "detail": "Attacker compromised a Safe{Wallet} developer machine and pushed tampered JS that altered what specific Bybit signers saw when approving.", "sourceIds": ["ncc-bybit"] }, + { "order": 2, "label": "Signers approve distorted calldata", "detail": "Three signers approved what looked like a cold-to-hot transfer; the wire format encoded a delegatecall to the attacker contract.", "sourceIds": ["ncc-bybit", "chainalysis-bybit"] }, + { "order": 3, "label": "Singleton swap", "detail": "delegatecall wrote slot 0 of the Safe to point at the attacker implementation, silently replacing the multisig's code.", "sourceIds": ["ncc-bybit"] }, + { "order": 4, "label": "Drain ~401k ETH", "detail": "Attacker's implementation transferred the full cold-wallet balance across many intermediary addresses.", "sourceIds": ["elliptic-bybit"] } + ], + "entities": [ + { "id": "victim-safe", "role": "victim-contract", "address": "0x1Db92e2EeBC8E0c075a02BeA49a2935BcD2dFCF4", "label": "Bybit cold wallet (Safe)" }, + { "id": "attacker-impl", "role": "attacker-contract", "address": "0xbDd077f651EBe7f7b3cE16fe5F2b025BE2969516", "label": "Attacker implementation" }, + { "id": "legit-singleton", "role": "implementation", "address": "0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F", "label": "Legit Safe singleton (pre-swap)" }, + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x0fa09C3A328792253f8dee7116848723b72a6d2e", "label": "Attacker EOA" } + ], + "fundFlow": [ + { "fromEntityId": "victim-safe", "toEntityId": "attacker-eoa", "tokenSymbol": "ETH", "amountHuman": "401,347 ETH", "note": "drained in one tx" } + ], + "sources": [ + { "id": "ncc-bybit", "kind": "forensic-thread", "url": "https://www.nccgroup.com/research/in-depth-technical-analysis-of-the-bybit-hack/", "publisher": "NCC Group" }, + { "id": "chainalysis-bybit", "kind": "official", "url": "https://www.chainalysis.com/blog/bybit-exchange-hack-february-2025-crypto-security-dprk/", "publisher": "Chainalysis" }, + { "id": "elliptic-bybit", "kind": "forensic-thread", "url": "https://www.elliptic.co/blog/bybit-hack-largest-in-history", "publisher": "Elliptic" } + ] +} diff --git a/src/utils/hack-analysis/incidents/curve-vyper-2023-07.json b/src/utils/hack-analysis/incidents/curve-vyper-2023-07.json new file mode 100644 index 0000000..70702cc --- /dev/null +++ b/src/utils/hack-analysis/incidents/curve-vyper-2023-07.json @@ -0,0 +1,162 @@ +{ + "id": "curve-vyper-2023-07", + "name": "Curve Finance Vyper Reentrancy Exploit", + "chain": "ethereum", + "date": "2023-07-30", + "protocol": "Curve Finance", + "amountUsd": 52000000, + "canonicalTxs": ["0xb676d789bb8b66a08105c844a49c2bcffb400e5c1cfabd4bc30cca4bff3c9801"], + "exploitClasses": ["reentrancy"], + "tldr": "A compiler bug in Vyper 0.2.15/0.2.16/0.3.0 silently broke the @nonreentrant guard, letting attackers reenter Curve pool functions and drain four pools (pETH, msETH, alETH, CRV/ETH) for ~$70M gross; ~$52M net after white-hat recoveries.", + "coreContradiction": "The @nonreentrant('lock') decorator compiled to separate storage slots per function, so the lock held by one function did not block reentry into a different function — the guard existed in source but was a no-op at runtime.", + "attackSteps": [ + { + "order": 1, + "label": "Flash-loan WETH", + "detail": "Attacker borrowed 40,000 WETH from Balancer's vault via flash loan, converting to ETH as needed for the Curve native-ETH pool.", + "sourceIds": ["llamarisk-pm"] + }, + { + "order": 2, + "label": "Add liquidity (first deposit)", + "detail": "Attacker deposited a large ETH tranche into the target Curve pool (e.g., alETH/ETH), receiving LP tokens. The pool's add_liquidity function was compiled with Vyper 0.3.0 which had the broken reentrancy lock.", + "sourceIds": ["llamarisk-pm", "certik-analysis"] + }, + { + "order": 3, + "label": "Trigger reentrancy via ETH callback", + "detail": "When the pool sent ETH back during remove_liquidity, the attacker contract's fallback received the ETH callback and immediately re-entered add_liquidity. Because the reentrancy lock slot for add_liquidity and remove_liquidity were distinct, the lock was not set for add_liquidity during the remove callback.", + "sourceIds": ["llamarisk-pm", "certik-analysis"] + }, + { + "order": 4, + "label": "Inflate LP token balance", + "detail": "The re-entered add_liquidity calculated LP tokens using stale pre-burn balances (the burn from remove_liquidity had not yet been finalized), minting far more LP tokens than warranted — effectively creating a fraudulent claim on the pool's reserves.", + "sourceIds": ["llamarisk-pm"] + }, + { + "order": 5, + "label": "Drain pool assets", + "detail": "Using the inflated LP token balance, the attacker withdrew far more ETH and pool tokens than deposited, repeating the cycle to extract the majority of pool liquidity.", + "sourceIds": ["llamarisk-pm", "certik-analysis"] + }, + { + "order": 6, + "label": "Repay flash loan, keep profit", + "detail": "Flash loan repaid to Balancer; net profit (~7,258 ETH and 4,821 alETH from the alETH pool alone) retained by the attacker.", + "sourceIds": ["llamarisk-pm"] + } + ], + "entities": [ + { + "id": "attacker-eoa-aleth", + "role": "attacker-eoa", + "address": "0xDCe5d6b41C32f578f875EfFfc0d422C57A75d7D8", + "label": "Alchemix/CurveFinance Exploiter (alETH attacker EOA)" + }, + { + "id": "attacker-contract-aleth", + "role": "attacker-contract", + "address": "0x30FB95794a2051ABe30A67892B3A1FA73947aEE5", + "label": "Alchemix/CurveFinance Exploit Contract" + }, + { + "id": "attacker-eoa-peth", + "role": "attacker-eoa", + "address": "0x6Ec21d1868743a44318c3C259a6d4953F9978538", + "label": "JPEG'd pETH Exploiter EOA (Frontrunner)" + }, + { + "id": "attacker-contract-peth", + "role": "attacker-contract", + "address": "0x9420F8821aB4609Ad9FA514f8D2F5344C3c0A6Ab", + "label": "JPEG'd pETH Exploit Contract" + }, + { + "id": "whitehat-eoa", + "role": "attacker-eoa", + "address": "0xC0ffeEBABE5D496B2DDE509f9fa189C25cF29671", + "label": "c0ffeebabe.eth (white-hat MEV bot — returned funds)" + }, + { + "id": "victim-aleth-pool", + "role": "pool", + "address": "0xC4C319E2D4d66CcA4464C0c2B32c9Bd23ebe784e", + "label": "Curve alETH/ETH pool (victim)" + }, + { + "id": "victim-peth-pool", + "role": "pool", + "address": "0x9848482da3Ee3076165ce6497eDA906E66bB85C5", + "label": "Curve pETH/ETH pool (victim)" + }, + { + "id": "victim-mseth-pool", + "role": "pool", + "address": "0xc897b98272AA23714464Ea2A0Bd5180f1B8C0025", + "label": "Curve msETH/ETH pool (victim)" + } + ], + "fundFlow": [ + { + "fromEntityId": "victim-aleth-pool", + "toEntityId": "attacker-eoa-aleth", + "tokenSymbol": "ETH", + "amountHuman": "7,258 ETH", + "note": "ETH drained from alETH/ETH pool (canonical highest-value malicious tx)" + }, + { + "fromEntityId": "victim-aleth-pool", + "toEntityId": "attacker-eoa-aleth", + "tokenSymbol": "alETH", + "amountHuman": "4,821 alETH", + "note": "alETH drained alongside ETH in same exploit; later returned by attacker" + }, + { + "fromEntityId": "victim-peth-pool", + "toEntityId": "attacker-eoa-peth", + "tokenSymbol": "ETH", + "amountHuman": "6,107 ETH", + "note": "ETH drained from pETH/ETH pool (~$11M)" + }, + { + "fromEntityId": "victim-mseth-pool", + "toEntityId": "whitehat-eoa", + "tokenSymbol": "ETH", + "amountHuman": "~1,556 ETH", + "note": "c0ffeebabe.eth front-ran the msETH exploit and returned ~$1.6M to Metronome" + } + ], + "sources": [ + { + "id": "llamarisk-pm", + "kind": "post-mortem", + "url": "https://hackmd.io/@LlamaRisk/BJzSKHNjn", + "publisher": "LlamaRisk" + }, + { + "id": "vyper-pm", + "kind": "official", + "url": "https://hackmd.io/@vyperlang/HJUgNMhs2", + "publisher": "Vyper" + }, + { + "id": "certik-analysis", + "kind": "forensic-thread", + "url": "https://www.certik.com/resources/blog/2qbPMcyJpR5UfoKrcjWxlQ-vyper-incident-anaylsis", + "publisher": "CertiK" + }, + { + "id": "chainalysis-curve", + "kind": "database-row", + "url": "https://www.chainalysis.com/blog/curve-finance-liquidity-pool-hack/", + "publisher": "Chainalysis" + }, + { + "id": "alchemix-pm", + "kind": "post-mortem", + "url": "https://alchemixfi.medium.com/curve-exploit-post-mortem-7142e78bc339", + "publisher": "Alchemix Finance" + } + ] +} diff --git a/src/utils/hack-analysis/incidents/euler-2023-03.json b/src/utils/hack-analysis/incidents/euler-2023-03.json new file mode 100644 index 0000000..041cc5c --- /dev/null +++ b/src/utils/hack-analysis/incidents/euler-2023-03.json @@ -0,0 +1,144 @@ +{ + "id": "euler-2023-03", + "name": "Euler Finance Flash Loan Exploit", + "chain": "ethereum", + "date": "2023-03-13", + "protocol": "Euler Finance", + "amountUsd": 197000000, + "canonicalTxs": ["0xc310a0affe2169d1f6feec1c63dbc7f7c62a887fa48795d327d4d2da2d6b111d"], + "exploitClasses": ["math-invariant-manipulation", "flashloan-price-manipulation"], + "tldr": "Euler Finance lost ~$197M when an attacker exploited a missing health-factor check in the donateToReserves function: flash-loaning DAI, building a leveraged eDAI position, donating collateral to reserves to push their own account insolvent, then self-liquidating for a discounted payout. Funds were ultimately returned after negotiations.", + "coreContradiction": "donateToReserves() let users permanently reduce their own collateral without any liquidity check, so an attacker could voluntarily push their account below a health score of 1 and then profit from Euler's own soft-liquidation discount mechanism.", + "attackSteps": [ + { + "order": 1, + "label": "Flash-loan 30M DAI", + "detail": "Attacker contract (Exploiter 3) borrowed 30 million DAI from Aave V2 via a flash loan to seed the attack capital.", + "sourceIds": ["cyfrin-euler", "slowmist-euler"] + }, + { + "order": 2, + "label": "Deposit and lever up via mint", + "detail": "Deposited 20M DAI into Euler, receiving ~19.57M eDAI, then called mint() twice to self-borrow: accumulating ~410.9M eDAI (collateral) against ~390M dDAI (debt) at a health score of ~1.02, with an intermediate 10M DAI partial repayment to reset leverage headroom.", + "sourceIds": ["cyfrin-euler", "rekt-euler"] + }, + { + "order": 3, + "label": "Donate 100M eDAI to reserves", + "detail": "Called donateToReserves() on the eDAI EToken to donate 100M eDAI to the Euler protocol reserve. Because donateToReserves lacked a post-donation liquidity check, the attacker's health score fell to ~0.75 — deep into insolvency — without any revert.", + "sourceIds": ["cyfrin-euler", "slowmist-euler", "rekt-euler"] + }, + { + "order": 4, + "label": "Self-liquidate via liquidator contract", + "detail": "A separate liquidator contract called Euler's liquidation module against the now-insolvent violator contract. At health score 0.75 Euler's soft-liquidation formula awarded a 20% collateral discount; the liquidator assumed ~259.3M dDAI debt but obtained ~310.9M eDAI collateral — capturing the discount as profit.", + "sourceIds": ["cyfrin-euler", "slowmist-euler"] + }, + { + "order": 5, + "label": "Withdraw DAI and repay flash loan", + "detail": "Liquidator redeemed eDAI for ~38.9M underlying DAI, repaid the 30M DAI Aave flash loan plus interest, and retained ~8.87M DAI net profit from the DAI pool alone. The same pattern was repeated across USDC, WBTC, wstETH, and stETH pools.", + "sourceIds": ["cyfrin-euler", "rekt-euler"] + } + ], + "entities": [ + { + "id": "attacker-eoa", + "role": "attacker-eoa", + "address": "0x5F259D0b76665c337c6104145894F4D1D2758B8c", + "label": "Euler Finance Exploiter 3 (canonical DAI tx sender)" + }, + { + "id": "exploit-contract", + "role": "attacker-contract", + "address": "0xeBC29199C817Dc47BA12E3F86102564D640CBf99", + "label": "Top-level exploit orchestrator contract" + }, + { + "id": "violator-contract", + "role": "attacker-contract", + "address": "0x583c21631c48D442B5C0E605d624f54A0B366c72", + "label": "Violator contract (holds insolvent position)" + }, + { + "id": "liquidator-contract", + "role": "attacker-contract", + "address": "0xA0b3ee897f233F385E5D61086c32685257d4f12b", + "label": "Liquidator contract (claims discounted collateral)" + }, + { + "id": "euler-core", + "role": "victim-contract", + "address": "0x27182842E098f60e3D576794A5bFFb0777E025d3", + "label": "Euler Finance main lending contract" + }, + { + "id": "etoken-dai", + "role": "token", + "address": "0xe025E3ca2bE02316033184551D4d3Aa22024D9DC", + "label": "eDAI (Euler interest-bearing DAI)" + }, + { + "id": "dtoken-dai", + "role": "token", + "address": "0x6085Bc95F506c326DCBCD7A6dd6c79FBc18d4686", + "label": "dDAI (Euler debt token)" + } + ], + "fundFlow": [ + { + "fromEntityId": "euler-core", + "toEntityId": "violator-contract", + "tokenSymbol": "DAI", + "amountHuman": "~200M DAI (eDAI minted via leverage)", + "note": "Euler mint() creates self-collateralized eDAI/dDAI positions" + }, + { + "fromEntityId": "violator-contract", + "toEntityId": "euler-core", + "tokenSymbol": "DAI", + "amountHuman": "100M eDAI", + "note": "donateToReserves() call drops health factor below 1 without check" + }, + { + "fromEntityId": "euler-core", + "toEntityId": "liquidator-contract", + "tokenSymbol": "DAI", + "amountHuman": "~38.9M DAI (DAI pool; ~$197M total across all pools)", + "note": "Self-liquidation at 20% discount extracts underlying; repeated for USDC, WBTC, wstETH, stETH" + }, + { + "fromEntityId": "liquidator-contract", + "toEntityId": "attacker-eoa", + "tokenSymbol": "DAI", + "amountHuman": "~8.87M DAI net per pool after flash loan repayment", + "note": "Profit retained after repaying 30M DAI Aave flash loan" + } + ], + "sources": [ + { + "id": "cyfrin-euler", + "kind": "post-mortem", + "url": "https://www.cyfrin.io/blog/how-did-the-euler-finance-hack-happen-hack-analysis", + "publisher": "Cyfrin" + }, + { + "id": "rekt-euler", + "kind": "post-mortem", + "url": "https://rekt.news/euler-rekt/", + "publisher": "Rekt News" + }, + { + "id": "slowmist-euler", + "kind": "forensic-thread", + "url": "https://slowmist.medium.com/slowmist-an-analysis-of-the-attack-on-euler-finance-5143abc0d5ad", + "publisher": "SlowMist" + }, + { + "id": "euler-official", + "kind": "official", + "url": "https://www.euler.finance/blog/war-peace-behind-the-scenes-of-eulers-240m-exploit-recovery", + "publisher": "Euler Finance" + } + ] +} diff --git a/src/utils/hack-analysis/incidents/harmony-2022-06.json b/src/utils/hack-analysis/incidents/harmony-2022-06.json new file mode 100644 index 0000000..495eea4 --- /dev/null +++ b/src/utils/hack-analysis/incidents/harmony-2022-06.json @@ -0,0 +1,42 @@ +{ + "id": "harmony-2022-06", + "name": "Harmony Horizon Bridge Validator Compromise", + "chain": "ethereum", + "date": "2022-06-23", + "protocol": "Harmony Horizon Bridge", + "amountUsd": 100000000, + "canonicalTxs": ["0x27981c7289c372e601c9475e5b5466310be18ed10b59d1ac840145f6e7804c97"], + "exploitClasses": ["signer-compromise", "bridge-message-forgery"], + "tldr": "Attackers phished and decrypted private keys for at least two of the five signers on the Horizon bridge multisig, then signed unauthorized unlock transactions that drained ~$100M in ETH, USDC, USDT, WBTC and other tokens from the Ethereum-side Horizon bridge contracts to an attacker EOA; the bridge spans Harmony and Ethereum and all canonical drain txs listed here are on Ethereum.", + "coreContradiction": "A cross-chain bridge holding $100M+ was guarded by a 2-of-5 multisig whose signer keys were stored as encrypted secrets on reachable internal servers, so a single phishing foothold collapsed the threshold.", + "attackSteps": [ + { "order": 1, "label": "Phish developer", "detail": "Attackers (later attributed to Lazarus/APT38) phished a Harmony developer with a fake job-offer lure, installed malware and pivoted onto internal Harmony infrastructure where bridge signer key material was stored.", "sourceIds": ["harmony-postmortem", "elliptic-harmony"] }, + { "order": 2, "label": "Decrypt signer keys", "detail": "From inside Harmony's operational infrastructure the attackers accessed and decrypted private keys for at least two of the five Horizon bridge multisig signers, enough to meet the 2-of-5 threshold on the Ethereum side.", "sourceIds": ["harmony-postmortem", "merkle-harmony"] }, + { "order": 3, "label": "Sign malicious unlockTokens", "detail": "Using signer EOAs 0xf845A7ee8477AD1FB4446651E548901a2635A915 and 0x812d8622C6F3c45959439e7ede3C580dA06f8f25 the attackers submitted and confirmed multisig transactions that invoked the Horizon bridge's unlock/owner paths, authorizing transfers to the attacker EOA.", "sourceIds": ["lazarus-research", "elliptic-harmony"] }, + { "order": 4, "label": "Drain ETH bridge", "detail": "At 11:06 UTC on 2022-06-23, tx 0x27981c...804c97 drained 13,100 ETH (~$29.7M) from the Harmony ETH Bridge (0xF9Fb1c508Ff49F78b60d3A96dea99Fa5d7F3A8A6) directly to the attacker EOA in a single Unlocked event.", "sourceIds": ["merkle-harmony", "lazarus-research"] }, + { "order": 5, "label": "Drain ERC20 bridge", "detail": "Additional unauthorized transactions against the Horizon ERC20 Bridge (0x2dCCDB493827E15a5dC8f8b72147E6c4A5620857) sent roughly 41.2M USDC, 9.98M USDT, 592 WBTC plus DAI, WETH, SUSHI, AAVE, FXS and FRAX to the same attacker EOA, bringing total losses to about $100M.", "sourceIds": ["merkle-harmony", "elliptic-harmony"] }, + { "order": 6, "label": "Launder via Tornado Cash", "detail": "The attacker swapped stolen tokens to ETH and, between June 27 and July 2, 2022, deposited roughly 85,700 ETH into Tornado Cash across about 857 transactions; Elliptic and later the FBI attributed the laundering pattern to the Lazarus Group.", "sourceIds": ["elliptic-harmony", "merkle-harmony"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x0d043128146654C7683Fbf30ac98D7B2285DeD00", "label": "Horizon Bridge Exploiter EOA" }, + { "id": "eth-bridge", "role": "bridge", "address": "0xF9Fb1c508Ff49F78b60d3A96dea99Fa5d7F3A8A6", "label": "Harmony Horizon ETH Bridge" }, + { "id": "erc20-bridge", "role": "bridge", "address": "0x2dCCDB493827E15a5dC8f8b72147E6c4A5620857", "label": "Harmony Horizon ERC20 Bridge" }, + { "id": "busd-bridge", "role": "bridge", "address": "0xfD53b1B4AF84D59B20bF2C20CA89a6BeeAa2c628", "label": "Harmony Horizon BUSD Bridge" }, + { "id": "signer-1", "role": "signer", "address": "0xf845A7ee8477AD1FB4446651E548901a2635A915", "label": "Compromised Horizon multisig signer #1" }, + { "id": "signer-2", "role": "signer", "address": "0x812d8622C6F3c45959439e7ede3C580dA06f8f25", "label": "Compromised Horizon multisig signer #2" } + ], + "fundFlow": [ + { "fromEntityId": "eth-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": "ETH", "amountHuman": "13,100 ETH", "note": "Single Unlocked tx 0x27981c...804c97, ~$29.7M at time of hack" }, + { "fromEntityId": "erc20-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": "USDC", "amountHuman": "41,200,000 USDC", "note": "Block 15012652, largest ERC20 drain" }, + { "fromEntityId": "erc20-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": "USDT", "amountHuman": "9,981,000 USDT", "note": "Block 15012671" }, + { "fromEntityId": "erc20-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": "WBTC", "amountHuman": "592 WBTC", "note": "Block 15012654" }, + { "fromEntityId": "erc20-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": null, "amountHuman": null, "note": "Additional drains: DAI, WETH, SUSHI, AAVE, FXS, FRAX" }, + { "fromEntityId": "busd-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": "BUSD", "amountHuman": "5,500,000 BUSD", "note": "BUSD bridge drain (reported by Merkle Science)" } + ], + "sources": [ + { "id": "harmony-postmortem", "kind": "post-mortem", "url": "https://talk.harmony.one/t/summary-of-the-horizon-bridge-incident/20990", "publisher": "Harmony" }, + { "id": "elliptic-harmony", "kind": "forensic-thread", "url": "https://www.elliptic.co/blog/analysis/over-1-billion-stolen-from-bridges-so-far-in-2022-as-harmony-s-horizon-bridge-becomes-latest-victim-in-100-million-hack/hss_channeltw-1344645140", "publisher": "Elliptic" }, + { "id": "merkle-harmony", "kind": "forensic-thread", "url": "https://www.merklescience.com/blog/hack-track-analysis-of-harmonys-horizon-bridge-exploit", "publisher": "Merkle Science" }, + { "id": "lazarus-research", "kind": "database-row", "url": "https://github.com/tayvano/lazarus-bluenoroff-research/blob/main/hacks-and-thefts/harmony_horizon_bridge.md", "publisher": "tayvano lazarus-bluenoroff-research" } + ] +} diff --git a/src/utils/hack-analysis/incidents/index.ts b/src/utils/hack-analysis/incidents/index.ts new file mode 100644 index 0000000..b495de5 --- /dev/null +++ b/src/utils/hack-analysis/incidents/index.ts @@ -0,0 +1,26 @@ +import { incidentSchema, validateCrossRefs, type Incident } from "../types"; + +const jsonModules = import.meta.glob>( + "./*.json", + { eager: true, import: "default" }, +); + +let cached: Incident[] | null = null; + +export function loadIncidents(): Incident[] { + if (cached) return cached; + const out: Incident[] = []; + for (const [path, mod] of Object.entries(jsonModules)) { + const parsed = incidentSchema.safeParse(mod); + if (!parsed.success) throw new Error(`Invalid incident JSON at ${path}: ${parsed.error.message}`); + const refErrs = validateCrossRefs(parsed.data); + if (refErrs.length) throw new Error(`Cross-ref errors in ${path}: ${refErrs.join("; ")}`); + out.push(parsed.data); + } + cached = out; + return out; +} + +export function getIncidentById(id: string): Incident | undefined { + return loadIncidents().find((i) => i.id === id); +} diff --git a/src/utils/hack-analysis/incidents/inverse-finance-2022-04.json b/src/utils/hack-analysis/incidents/inverse-finance-2022-04.json new file mode 100644 index 0000000..6be21eb --- /dev/null +++ b/src/utils/hack-analysis/incidents/inverse-finance-2022-04.json @@ -0,0 +1,47 @@ +{ + "id": "inverse-finance-2022-04", + "name": "Inverse Finance Anchor Oracle Manipulation", + "chain": "ethereum", + "date": "2022-04-02", + "protocol": "Inverse Finance", + "amountUsd": 15600000, + "canonicalTxs": [ + "0x20a6dcff06a791a7f8be9f423053ce8caee3f9eecc31df32445fc98d4ccd8365", + "0x600373f67521324c8068cfd025f121a0843d57ec813411661b07edc5ff781842" + ], + "exploitClasses": ["oracle-manipulation", "flashloan-price-manipulation"], + "tldr": "Inverse Finance's Anchor lending market lost ~$15.6M when an attacker pumped the INV/WETH SushiSwap TWAP feed that Anchor's Keep3r oracle read, then borrowed ETH, WBTC, YFI, and DOLA against a tiny amount of INV collateral priced at the manipulated rate.", + "coreContradiction": "Anchor trusted a Keep3r TWAP rooted in a thinly traded SushiSwap INV/WETH pool, so collateral value was decided by the same pool a single actor could move with ~500 ETH of swap volume.", + "attackSteps": [ + { "order": 1, "label": "Fund from Tornado", "detail": "Attacker drew 901 ETH from Tornado Cash and spread 1.5 ETH across 241 fresh addresses via Disperse to pay gas while spamming blocks around the oracle update.", "sourceIds": ["rekt-inverse"] }, + { "order": 2, "label": "Pump INV price via SushiSwap", "detail": "Attacker EOA 0x8B4C... ran tx 0x20a6dc... through the exploit contract, swapping WETH->INV through the shallow SushiSwap INV/WETH pair (and the INV/DOLA route) to push INV's price ~50x on the Keep3r TWAP feed.", "sourceIds": ["rekt-inverse", "certik-inverse"] }, + { "order": 3, "label": "Spam blocks to freeze the TWAP", "detail": "Gas-bribed spam transactions ensured the inflated SushiSwap price was observed by Keep3r's TWAP update before arbitrage could revert it, so Anchor read the manipulated price.", "sourceIds": ["rekt-inverse"] }, + { "order": 4, "label": "Deposit INV, borrow everything", "detail": "Second EOA 0x117C... called mint() on the attacker contract (tx 0x600373...) which deposited ~1,746 INV into anINV (xINV), entered the markets, and borrowed 1,588 ETH, 94 WBTC, 39.37 YFI, and 3,999,669 DOLA from anETH/anWBTC/anYFI/anDOLA.", "sourceIds": ["certik-inverse", "rekt-inverse"] }, + { "order": 5, "label": "Exit through Tornado", "detail": "~$15.6M of borrowed assets were swapped to ETH and funneled back through Tornado Cash while the INV collateral was left behind to be liquidated at a fraction of the borrow value.", "sourceIds": ["rekt-inverse", "coindesk-inverse"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x8B4C1083cd6Aef062298E1Fa900df9832c8351b3", "label": "Inverse Exploiter 2 (price-pump EOA)" }, + { "id": "attacker-eoa-2", "role": "attacker-eoa", "address": "0x117C0391B3483E32AA665b5ecb2Cc539669EA7E9", "label": "Inverse Exploiter 1 (borrow EOA)" }, + { "id": "attacker-contract", "role": "attacker-contract", "address": "0xeA0c959BBb7476DDD6cD4204bDee82b790AA1562", "label": "Exploit orchestrator contract" }, + { "id": "anchor-comptroller", "role": "victim-contract", "address": "0x4dCf7407AE5C07f8681e1659f626E114A7667339", "label": "Inverse Anchor Unitroller (Comptroller)" }, + { "id": "an-eth", "role": "victim-contract", "address": "0x697b4acAa24430F254224eB794d2a85ba1Fa1FB8", "label": "anETH market" }, + { "id": "an-wbtc", "role": "victim-contract", "address": "0x17786f3813E6bA35343211bd8Fe18EC4de14F28b", "label": "anWBTC market" }, + { "id": "an-yfi", "role": "victim-contract", "address": "0xde2af899040536884e062D3a334F2dD36F34b4a4", "label": "anYFI market (pre-deprecation)" }, + { "id": "an-dola", "role": "victim-contract", "address": "0x7Fcb7DAC61eE35b3D4a51117A7c58D53f0a8a670", "label": "anDOLA market" }, + { "id": "sushi-inv-weth", "role": "oracle", "address": "0x328dFd0139e26cB0FEF7B0742B49b0fe4325F821", "label": "SushiSwap INV/WETH pair (manipulated TWAP source)" }, + { "id": "inv-token", "role": "token", "address": "0x41D5D79431A913C4aE7d69a668ecdfE5fF9DFB68", "label": "INV governance token" }, + { "id": "dola-token", "role": "token", "address": "0x865377367054516e17014CcdED1e7d814EDC9ce4", "label": "DOLA stablecoin" } + ], + "fundFlow": [ + { "fromEntityId": "an-eth", "toEntityId": "attacker-contract", "tokenSymbol": "ETH", "amountHuman": "1,588.26 ETH", "note": "borrowed against manipulated INV collateral" }, + { "fromEntityId": "an-wbtc", "toEntityId": "attacker-contract", "tokenSymbol": "WBTC", "amountHuman": "94.03 WBTC", "note": "borrowed against manipulated INV collateral" }, + { "fromEntityId": "an-yfi", "toEntityId": "attacker-contract", "tokenSymbol": "YFI", "amountHuman": "39.37 YFI", "note": "borrowed against manipulated INV collateral" }, + { "fromEntityId": "an-dola", "toEntityId": "attacker-contract", "tokenSymbol": "DOLA", "amountHuman": "3,999,669 DOLA", "note": "borrowed against manipulated INV collateral" }, + { "fromEntityId": "attacker-contract", "toEntityId": "attacker-eoa-2", "tokenSymbol": null, "amountHuman": null, "note": "proceeds consolidated to Exploiter 1 EOA before Tornado Cash" } + ], + "sources": [ + { "id": "rekt-inverse", "kind": "forensic-thread", "url": "https://rekt.news/inverse-finance-rekt/", "publisher": "rekt.news" }, + { "id": "certik-inverse", "kind": "forensic-thread", "url": "https://www.certik.com/resources/blog/3HEBdgXIaSemB73rTFNrXT-inverse-finance-02-april-2022", "publisher": "CertiK" }, + { "id": "coindesk-inverse", "kind": "database-row", "url": "https://www.coindesk.com/tech/2022/04/02/defi-lender-inverse-finance-exploited-for-156-million", "publisher": "CoinDesk" } + ] +} diff --git a/src/utils/hack-analysis/incidents/kyberswap-elastic-2023-11.json b/src/utils/hack-analysis/incidents/kyberswap-elastic-2023-11.json new file mode 100644 index 0000000..f9d6c29 --- /dev/null +++ b/src/utils/hack-analysis/incidents/kyberswap-elastic-2023-11.json @@ -0,0 +1,37 @@ +{ + "id": "kyberswap-elastic-2023-11", + "name": "KyberSwap Elastic Tick-Crossing Drain", + "chain": "ethereum", + "date": "2023-11-22", + "protocol": "KyberSwap Elastic", + "amountUsd": 48000000, + "canonicalTxs": ["0x485e08dc2b6a4b3aeadcb89c3d18a37666dc7d9424961a2091d6b3696792f0f3"], + "exploitClasses": ["math-invariant-manipulation"], + "tldr": "KyberSwap Elastic lost ~$48M across multiple chains when an attacker exploited a precision/tick-crossing flaw in the concentrated-liquidity swap math; the largest single-chain drain was on Ethereum, with parallel drains on Arbitrum, Optimism, Polygon, Base, Avalanche, and BSC pools.", + "coreContradiction": "Because computeSwapStep rounded the pre-tick amountIn using base+reinvestment liquidity but the post-swap price check used only base liquidity, the pool could cross a tick without ever calling _updateLiquidityAndCrossTick, letting the same liquidity be counted on both sides of the boundary.", + "attackSteps": [ + { "order": 1, "label": "Flash loan WETH", "detail": "Attacker EOA 0x5027...be836 deployed exploit contract 0xaF2A...CDC13 and funded it with ~2,000 WETH flash-loaned from AAVE on Ethereum.", "sourceIds": ["kyberswap-postmortem", "slowmist-kyber"] }, + { "order": 2, "label": "Price push past LP range", "detail": "Swap ~6.85 WETH into the frxETH/WETH KyberSwap Elastic pool (0xFd7B...4703) to push sqrtPrice above every existing LP position's upper tick, leaving the pool with zero active base liquidity.", "sourceIds": ["blocksec-kyber", "slowmist-kyber"] }, + { "order": 3, "label": "Mint and partially burn", "detail": "Attacker minted a tiny position in the narrow range [tick 110909, 111310] and burned most of it so that only their liquidity was active inside that range while the reinvestment curve still held residual liquidity.", "sourceIds": ["blocksec-kyber", "halborn-kyber"] }, + { "order": 4, "label": "Trigger double-count tick cross", "detail": "A precisely-sized reverse swap made computeSwapStep overestimate amountIn (it folded in reinvestment liquidity) so post-swap sqrtPrice exceeded the boundary tick without satisfying the strict inequality that calls _updateLiquidityAndCrossTick; liquidity was effectively counted twice.", "sourceIds": ["blocksec-kyber", "kyberswap-postmortem"] }, + { "order": 5, "label": "Drain pool and repeat", "detail": "The flawed accounting let the attacker pull ~396 WETH out for ~0.006 frxETH, ~9 WETH more than the forward leg; the exploit contract repeated the pattern across multiple Ethereum Elastic pools and replayed it on six other chains for a total ~$48M loss.", "sourceIds": ["rekt-kyber", "kyberswap-postmortem"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x50275E0B7261559cE1644014d4b78D4AA63BE836", "label": "KyberSwap Exploiter 1 (EOA)" }, + { "id": "attacker-contract", "role": "attacker-contract", "address": "0xaF2Acf3D4ab78e4c702256D214a3189A874CDC13", "label": "Exploit contract" }, + { "id": "victim-pool", "role": "pool", "address": "0xFd7B111AA83b9b6F547E617C7601EfD997F64703", "label": "KyberSwap Elastic frxETH/WETH pool" }, + { "id": "elastic-factory", "role": "victim-contract", "address": "0xC7a590291e07B9fe9E64b86c58fD8fC764308C4A", "label": "KyberSwap Elastic Factory" }, + { "id": "position-manager", "role": "router", "address": "0xe222fBE074A436145b255442D919E4E3A6c6a480", "label": "KyberSwap Elastic Anti-Sniping Position Manager" } + ], + "fundFlow": [ + { "fromEntityId": "victim-pool", "toEntityId": "attacker-contract", "tokenSymbol": "WETH", "amountHuman": "~396 WETH", "note": "net drain from frxETH/WETH pool via reverse swap; contract repeated pattern on other Elastic pools" }, + { "fromEntityId": "attacker-contract", "toEntityId": "attacker-eoa", "tokenSymbol": null, "amountHuman": null, "note": "proceeds forwarded to KyberSwap Exploiter 1 EOA after flash-loan repayment" } + ], + "sources": [ + { "id": "kyberswap-postmortem", "kind": "post-mortem", "url": "https://blog.kyberswap.com/post-mortem-kyberswap-elastic-exploit/", "publisher": "KyberSwap" }, + { "id": "rekt-kyber", "kind": "database-row", "url": "https://rekt.news/kyberswap-rekt/", "publisher": "rekt.news" }, + { "id": "blocksec-kyber", "kind": "forensic-thread", "url": "https://blocksec.com/blog/yet-another-tragedy-of-precision-loss-an-in-depth-analysis-of-the-kyber-swap-incident-1", "publisher": "BlockSec" }, + { "id": "slowmist-kyber", "kind": "forensic-thread", "url": "https://slowmist.medium.com/a-deep-dive-into-the-kyberswap-hack-3e13f3305d3a", "publisher": "SlowMist" }, + { "id": "halborn-kyber", "kind": "forensic-thread", "url": "https://www.halborn.com/blog/post/explained-the-kyberswap-hack-november-2023", "publisher": "Halborn" } + ] +} diff --git a/src/utils/hack-analysis/incidents/ledger-connect-2023-12.json b/src/utils/hack-analysis/incidents/ledger-connect-2023-12.json new file mode 100644 index 0000000..95b177f --- /dev/null +++ b/src/utils/hack-analysis/incidents/ledger-connect-2023-12.json @@ -0,0 +1,32 @@ +{ + "id": "ledger-connect-2023-12", + "name": "Ledger Connect Kit Supply-Chain Drainer", + "chain": "ethereum", + "date": "2023-12-14", + "protocol": "Ledger Connect Kit", + "amountUsd": 610000, + "canonicalTxs": ["0xf2b76233561ded9382612352eb4cd511e081b715b577c2e2236704af9e8050f4"], + "exploitClasses": ["approval-drain"], + "tldr": "A former Ledger employee's npm session token was phished, letting Angel Drainer publish malicious @ledgerhq/connect-kit 1.1.5/1.1.6/1.1.7 that injected a wallet-drainer into every dApp (SushiSwap, Zapper, Revoke.cash, Kyber, Balancer front-ends) loading the library for ~5 hours, harvesting ERC20 approvals and draining ~$610k before Ledger rotated the package.", + "coreContradiction": "The CDN-loaded Connect Kit was trusted as Ledger-signed dApp infrastructure, yet a single upstream npm publish token let an attacker silently swap the wallet-connect flow for an approve(drainer, max) prompt on every integrating dApp.", + "attackSteps": [ + { "order": 1, "label": "npm session token phished", "detail": "A former Ledger employee was phished; despite 2FA, the attacker replayed the stolen session token to regain publish rights on the @ledgerhq/connect-kit npm package.", "sourceIds": ["ledger-postmortem", "slowmist-ledger"] }, + { "order": 2, "label": "Malicious versions published", "detail": "Attacker published @ledgerhq/connect-kit versions 1.1.5, 1.1.6, and 1.1.7 containing a drainer payload that replaced the normal window provider with a Drainer class loading from a rogue WalletConnect project.", "sourceIds": ["ledger-postmortem", "slowmist-ledger"] }, + { "order": 3, "label": "dApps auto-pull poisoned CDN", "detail": "Any dApp using connect-kit-loader dynamically fetched the tampered bundle from jsDelivr/unpkg, so SushiSwap, Zapper, Revoke.cash, Kyber and others began serving the drainer to end users.", "sourceIds": ["slowmist-ledger", "hacken-ledger"] }, + { "order": 4, "label": "Victims sign approve / permit", "detail": "The injected Drainer popup prompted users to sign approve(drainer, max), setApprovalForAll, or EIP-2612 permit messages for ERC20s and NFTs, disguised as normal wallet-connect flows.", "sourceIds": ["slowmist-ledger", "hacken-ledger"] }, + { "order": 5, "label": "transferFrom pulls to drainer EOA", "detail": "Once signatures landed, the attacker called transferFrom / permitted pulls, consolidating drained tokens into the Angel Drainer EOA with an 85/15 split to the fee-collector address.", "sourceIds": ["slowmist-ledger", "ledger-postmortem"] }, + { "order": 6, "label": "Tether freezes drainer", "detail": "At 14:55 UTC on Dec 14 2023 the Tether multisig executed confirmTransaction(2665), emitting AddedBlackList for the drainer EOA and halting USDT movement; Ledger rotated to 1.1.8 shortly after.", "sourceIds": ["ledger-postmortem"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x658729879fCa881D9526480B82aE00EFc54B5c2d", "label": "Angel Drainer exploiter EOA (Ledger Exploiter)" }, + { "id": "drainer-fee", "role": "attacker-eoa", "address": "0x412f10AAd96fD78da6736387e2C84931Ac20313f", "label": "Angel Drainer 15% fee-collector EOA" } + ], + "fundFlow": [ + { "fromEntityId": "attacker-eoa", "toEntityId": "drainer-fee", "tokenSymbol": null, "amountHuman": "~15% of drained value", "note": "Angel Drainer-as-a-Service split: 85% to exploiter EOA, 15% to fee address" } + ], + "sources": [ + { "id": "ledger-postmortem", "kind": "post-mortem", "url": "https://www.ledger.com/blog/security-incident-report", "publisher": "Ledger" }, + { "id": "slowmist-ledger", "kind": "forensic-thread", "url": "https://slowmist.medium.com/supply-chain-attack-on-ledger-connect-kit-analyzing-the-impact-and-preventive-measures-1005e39422fd", "publisher": "SlowMist" }, + { "id": "hacken-ledger", "kind": "forensic-thread", "url": "https://hacken.io/insights/ledger-hack-explained/", "publisher": "Hacken" } + ] +} diff --git a/src/utils/hack-analysis/incidents/multichain-2023-07.json b/src/utils/hack-analysis/incidents/multichain-2023-07.json new file mode 100644 index 0000000..253ad87 --- /dev/null +++ b/src/utils/hack-analysis/incidents/multichain-2023-07.json @@ -0,0 +1,38 @@ +{ + "id": "multichain-2023-07", + "name": "Multichain (AnySwap) MPC Signer Drain", + "chain": "ethereum", + "date": "2023-07-06", + "protocol": "Multichain", + "amountUsd": 125000000, + "canonicalTxs": [ + "0x53ede4462d90978b992b0a88727de19afe4e96f0374aa1a221b8ff65fda5a6fe", + "0x48dc95a39e4c2c41ad3bdfadeddd5b7f57f87d83f07b3a9c3e49727f957da2ec" + ], + "exploitClasses": ["signer-compromise"], + "tldr": "Multichain's MPC bridge signer keys, concentrated in CEO Zhaojun's hot custody, were used by unknown parties after his arrest by Chinese police to sweep ~$125M of WETH, WBTC, USDC, DAI and other tokens from Fantom, Moonriver, Dogechain and Ethereum bridge vaults between July 6-7, 2023.", + "coreContradiction": "The bridge marketed itself as a decentralized MPC network, but the signer shares were in effect controlled by a single person's hot wallet, so once that person was detained the 'multi-party' infrastructure behaved as one fully compromised key.", + "attackSteps": [ + { "order": 1, "label": "CEO arrested", "detail": "Multichain CEO Zhaojun was detained by Chinese authorities in late May 2023; his laptops, phones and hardware controlling the MPC signer shards were seized along with him.", "sourceIds": ["chainalysis-multichain", "coindesk-multichain"] }, + { "order": 2, "label": "MPC signers go dark then reactivate", "detail": "After weeks of silent operation, the supposedly decentralized MPC nodes began signing unauthorized withdrawals on July 6, 2023, indicating that whoever controlled Zhaojun's keys had regained the ability to produce valid signatures.", "sourceIds": ["cointelegraph-executor", "chainalysis-multichain"] }, + { "order": 3, "label": "Bridge vaults swept", "detail": "Via the Multichain Executor EOA, legitimate anySwapFeeTo and ERC20 transfer calls were issued against the Router V4 and underlying token contracts, moving ~7,214 WETH, ~1,024 WBTC, $58M USDC and other assets out of the Ethereum-side Fantom bridge vault plus Moonriver and Dogechain vaults.", "sourceIds": ["cointelegraph-executor", "coindesk-multichain"] }, + { "order": 4, "label": "Funds routed to fresh EOAs", "detail": "Proceeds landed in previously-unseen addresses such as 0x1eED63... and 0x9D57b3...; Multichain later claimed Zhaojun's sister had moved residual funds for 'asset preservation' before she too was taken into custody, and the team announced indefinite suspension on July 7, 2023.", "sourceIds": ["chainalysis-multichain", "cointelegraph-executor"] } + ], + "entities": [ + { "id": "multichain-router-v4", "role": "bridge", "address": "0x6b7a87899490EcE95443e979cA9485CBE7E71522", "label": "Multichain Router V4 (Ethereum)" }, + { "id": "multichain-executor", "role": "signer", "address": "0x2A038e100F8B85DF21e4d44121bdBfE0c288A869", "label": "Multichain Executor (MPC-signed EOA)" }, + { "id": "attacker-eoa-primary", "role": "attacker-eoa", "address": "0x1eED63EfBA5f81D95bfe37d82C8E736b974F477b", "label": "Primary drain recipient EOA" }, + { "id": "attacker-eoa-secondary", "role": "attacker-eoa", "address": "0x9D57b3703F9438D8c1dd6b3af2A11ff39E7Ae37A", "label": "Secondary drain recipient EOA" }, + { "id": "dai-token", "role": "token", "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", "label": "DAI stablecoin" } + ], + "fundFlow": [ + { "fromEntityId": "multichain-router-v4", "toEntityId": "multichain-executor", "tokenSymbol": "anyDAI", "amountHuman": "15,275.90 anyDAI", "note": "anySwapFeeTo conversion extracting anyDAI reserves" }, + { "fromEntityId": "multichain-executor", "toEntityId": "attacker-eoa-primary", "tokenSymbol": "DAI", "amountHuman": "15,275.90 DAI", "note": "followup ERC20 transfer of redeemed DAI" }, + { "fromEntityId": "multichain-router-v4", "toEntityId": "attacker-eoa-secondary", "tokenSymbol": "mixed (DAI/LINK/USDT/WOO/CRV/YFI/TUSD)", "amountHuman": "~$16M equivalent", "note": "altcoin bucket swept out of Fantom bridge vault" } + ], + "sources": [ + { "id": "chainalysis-multichain", "kind": "forensic-thread", "url": "https://www.chainalysis.com/blog/multichain-exploit-july-2023/", "publisher": "Chainalysis" }, + { "id": "cointelegraph-executor", "kind": "forensic-thread", "url": "https://cointelegraph.com/news/multichain-executor-has-been-draining-anyswap-tokens-report", "publisher": "Cointelegraph" }, + { "id": "coindesk-multichain", "kind": "post-mortem", "url": "https://www.coindesk.com/business/2023/07/06/multichain-bridges-experience-unannounced-outflows-of-over-130m-in-crypto", "publisher": "CoinDesk" } + ] +} diff --git a/src/utils/hack-analysis/incidents/nomad-2022-08.json b/src/utils/hack-analysis/incidents/nomad-2022-08.json new file mode 100644 index 0000000..3ac9437 --- /dev/null +++ b/src/utils/hack-analysis/incidents/nomad-2022-08.json @@ -0,0 +1,112 @@ +{ + "id": "nomad-2022-08", + "name": "Nomad Bridge Free-For-All Drain", + "chain": "ethereum", + "date": "2022-08-01", + "protocol": "Nomad", + "amountUsd": 190000000, + "canonicalTxs": ["0x61497a1a8a8659a06358e130ea590e1eed8956edbd99dbb2048cfb46850a8f17"], + "exploitClasses": ["bridge-message-forgery", "access-control-bypass"], + "tldr": "A routine upgrade to Nomad's Replica contract initialized the trusted Merkle root to 0x00, causing the message-proof check to auto-pass for any arbitrary message. Within ~2.5 hours, hundreds of copy-cat attackers drained ~$190M in a permissionless free-for-all.", + "coreContradiction": "The contract accepted any message as 'proven' because the trusted root was the same zero value returned for any unmapped key, making forgery indistinguishable from legitimacy.", + "attackSteps": [ + { + "order": 1, + "label": "Bad initialize() sets root to 0x00", + "detail": "A June 21 2022 upgrade to the Replica implementation called initialize() with committedRoot = bytes32(0), which wrote confirmAt[0x00] = 1 — marking the zero hash as a permanently trusted root.", + "sourceIds": ["coinbase-nomad", "immunefi-nomad"] + }, + { + "order": 2, + "label": "acceptableRoot() returns true for any message", + "detail": "process() calls acceptableRoot(messages[msgHash]). For any message never previously stored, messages[] returns the default bytes32(0), which confirmAt maps to 1 (block 1, already passed), so the check passes unconditionally.", + "sourceIds": ["coinbase-nomad", "halborn-nomad"] + }, + { + "order": 3, + "label": "First attacker crafts 100 WBTC withdrawal", + "detail": "Exploiter 1 (0x56D8B635…) used a helper contract and Flashbots to privately submit a forged process() message instructing the ERC20 bridge to release 100 WBTC (~$2.3M) to their address in block 15259101.", + "sourceIds": ["coinbase-nomad", "rekt-nomad"] + }, + { + "order": 4, + "label": "Copy-cat replay drains remaining ~$188M", + "detail": "Once the exploit tx appeared on-chain, anyone could copy the calldata, replace the recipient address, and replay it. Nearly 300 addresses drained the bridge across all ERC-20 tokens over ~150 minutes.", + "sourceIds": ["rekt-nomad", "immunefi-nomad"] + } + ], + "entities": [ + { + "id": "victim-replica", + "role": "victim-contract", + "address": "0xB92336759618F55bd0F8313bd843604592E27bd8", + "label": "Nomad Replica (Ethereum)" + }, + { + "id": "victim-erc20bridge", + "role": "victim-contract", + "address": "0x88A69B4E698A4B090DF6CF5Bd7B2D47325Ad30A3", + "label": "Nomad ERC20 Bridge" + }, + { + "id": "attacker-eoa", + "role": "attacker-eoa", + "address": "0x56D8B635A7C88Fd1104D23d632AF40c1C3Aac4e3", + "label": "Nomad Bridge Exploiter 1" + }, + { + "id": "attacker-helper", + "role": "attacker-contract", + "address": "0xf57113D8f6Ff35747737f026fE0B37D4D7f42777", + "label": "Exploiter 1 helper contract" + }, + { + "id": "nomad-recovery", + "role": "router", + "address": "0x94A84433101A10aEda762968f6995c574D1bF154", + "label": "Nomad recovery wallet" + } + ], + "fundFlow": [ + { + "fromEntityId": "victim-erc20bridge", + "toEntityId": "attacker-eoa", + "tokenSymbol": "WBTC", + "amountHuman": "100 WBTC", + "note": "First exploit tx — canonical drain of 100 WBTC (~$2.3M) in block 15259101" + }, + { + "fromEntityId": "victim-erc20bridge", + "toEntityId": "nomad-recovery", + "tokenSymbol": null, + "amountHuman": "~$37M", + "note": "Partial return by white-hat copy-cat exploiters after Nomad's recovery bounty" + } + ], + "sources": [ + { + "id": "coinbase-nomad", + "kind": "post-mortem", + "url": "https://medium.com/the-coinbase-blog/nomad-bridge-incident-analysis-899b425b0f34", + "publisher": "Coinbase" + }, + { + "id": "immunefi-nomad", + "kind": "forensic-thread", + "url": "https://medium.com/immunefi/hack-analysis-nomad-bridge-august-2022-5aa63d53814a", + "publisher": "Immunefi" + }, + { + "id": "rekt-nomad", + "kind": "post-mortem", + "url": "https://rekt.news/nomad-rekt/", + "publisher": "rekt.news" + }, + { + "id": "halborn-nomad", + "kind": "post-mortem", + "url": "https://www.halborn.com/blog/post/explained-the-nomad-hack-august-2022", + "publisher": "Halborn" + } + ] +} diff --git a/src/utils/hack-analysis/incidents/orbit-bridge-2024-01.json b/src/utils/hack-analysis/incidents/orbit-bridge-2024-01.json new file mode 100644 index 0000000..a5c5e9d --- /dev/null +++ b/src/utils/hack-analysis/incidents/orbit-bridge-2024-01.json @@ -0,0 +1,46 @@ +{ + "id": "orbit-bridge-2024-01", + "name": "Orbit Bridge Multisig Drain", + "chain": "ethereum", + "date": "2024-01-01", + "protocol": "Orbit Bridge", + "amountUsd": 81500000, + "canonicalTxs": [ + "0xafdc36278fcef8d54824b09ec019147cfe2afd995abf6754e52d273a2c1b07ca", + "0xe0bada18fdc56dec125c31b1636490f85ba66016318060a066ed7050ff7271f9", + "0x639d27e564214411ad8eb06cf00d85cd90f83503a53ab5bf35dd5c6e1148ae0a", + "0x64a6f486c20671e1389b3c7948d46733325c407245a86bf510cb69ef401a3f0e", + "0xd8ca42941a0a2c25669267ad8d61f7f9f4118252cb502316602fe16624b80ac8" + ], + "exploitClasses": ["signer-compromise", "bridge-message-forgery"], + "tldr": "On New Year's Day 2024 an attacker controlling 7 of 10 Orbit Bridge multisig signer keys forged valid withdrawal authorizations and drained $81.5M in ETH, DAI, USDC, USDT, and wBTC from the Ethereum-side vault. The attack is attributed to North Korea's Lazarus Group.", + "coreContradiction": "The bridge's 5-of-7 signing threshold was meant to distribute trust, but attacker control of 7 signers turned the quorum into a single point of failure.", + "attackSteps": [ + { "order": 1, "label": "Signer keys stolen", "detail": "Attacker (suspected Lazarus Group) obtained private keys for 7 of the 10 Orbit Bridge multisig signers, likely via social engineering or the deliberately weakened firewall policies set by Ozys' former CISO in November 2023.", "sourceIds": ["orbit-official", "halborn-orbit"] }, + { "order": 2, "label": "Tornado Cash funding", "detail": "Attacker funded the exploit wallet via Tornado Cash through intermediary address 0x70462bFB204BF3CcB0560f259072F8E3A85b3512 to obscure the trail.", "sourceIds": ["rekt-orbit"] }, + { "order": 3, "label": "Forge withdrawal messages", "detail": "Using 7 compromised signer keys, the attacker produced valid multisig-signed withdrawal messages to the Orbit Bridge Ethereum vault, satisfying the signing threshold.", "sourceIds": ["rekt-orbit", "halborn-orbit"] }, + { "order": 4, "label": "Drain DAI (10M)", "detail": "First withdrawal: 10,000,000 DAI transferred from the ETH vault to the attacker at 21:08 UTC on December 31 / January 1.", "sourceIds": ["rekt-orbit"] }, + { "order": 5, "label": "Drain WBTC (231)", "detail": "231 WBTC (~$9.8M) drained from the ETH vault in the second withdrawal transaction.", "sourceIds": ["rekt-orbit"] }, + { "order": 6, "label": "Drain ETH (9,500)", "detail": "9,500 ETH (~$21.5M) drained from the ETH vault.", "sourceIds": ["rekt-orbit"] }, + { "order": 7, "label": "Drain USDC (10M)", "detail": "10,000,000 USDC drained from the ETH vault.", "sourceIds": ["rekt-orbit"] }, + { "order": 8, "label": "Drain USDT (30M)", "detail": "30,000,000 USDT drained from the ETH vault at 21:25 UTC, completing the five-asset drain totaling $81.5M.", "sourceIds": ["rekt-orbit"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x9263e7873613DDc598a701709875634819176AfF", "label": "Attacker primary EOA" }, + { "id": "attacker-intermediary", "role": "attacker-eoa", "address": "0x70462bFB204BF3CcB0560f259072F8E3A85b3512", "label": "Tornado Cash intermediary" }, + { "id": "victim-vault", "role": "victim-contract", "address": "0x1Bf68A9d1EaEe7826b3593C20a0ca93293cb489a", "label": "Orbit Bridge ETH Vault" } + ], + "fundFlow": [ + { "fromEntityId": "victim-vault", "toEntityId": "attacker-eoa", "tokenSymbol": "DAI", "amountHuman": "10,000,000 DAI", "note": "First withdrawal" }, + { "fromEntityId": "victim-vault", "toEntityId": "attacker-eoa", "tokenSymbol": "WBTC", "amountHuman": "231 WBTC", "note": "Second withdrawal ~$9.8M" }, + { "fromEntityId": "victim-vault", "toEntityId": "attacker-eoa", "tokenSymbol": "ETH", "amountHuman": "9,500 ETH", "note": "Third withdrawal ~$21.5M" }, + { "fromEntityId": "victim-vault", "toEntityId": "attacker-eoa", "tokenSymbol": "USDC", "amountHuman": "10,000,000 USDC", "note": "Fourth withdrawal" }, + { "fromEntityId": "victim-vault", "toEntityId": "attacker-eoa", "tokenSymbol": "USDT", "amountHuman": "30,000,000 USDT", "note": "Fifth withdrawal, completing drain" } + ], + "sources": [ + { "id": "rekt-orbit", "kind": "post-mortem", "url": "https://rekt.news/orbit-bridge-rekt", "publisher": "Rekt News" }, + { "id": "orbit-official", "kind": "official", "url": "https://medium.com/orbit-chain/official-statement-regarding-orbit-bridge-exploit-551928f3dc52", "publisher": "Orbit Chain" }, + { "id": "halborn-orbit", "kind": "post-mortem", "url": "https://www.halborn.com/blog/post/explained-the-orbit-bridge-hack-december-2023", "publisher": "Halborn" }, + { "id": "dn-orbit", "kind": "database-row", "url": "https://dn.institute/research/cyberattacks/incidents/2023-12-31-orbit-bridge/", "publisher": "Distributed Networks Institute" } + ] +} diff --git a/src/utils/hack-analysis/incidents/orion-protocol-2023-02.json b/src/utils/hack-analysis/incidents/orion-protocol-2023-02.json new file mode 100644 index 0000000..dff19f8 --- /dev/null +++ b/src/utils/hack-analysis/incidents/orion-protocol-2023-02.json @@ -0,0 +1,117 @@ +{ + "id": "orion-protocol-2023-02", + "name": "Orion Protocol Reentrancy Exploit", + "chain": "ethereum", + "date": "2023-02-02", + "protocol": "Orion Protocol", + "amountUsd": 2900000, + "canonicalTxs": ["0xa6f63fcb6bec8818864d96a5b1bb19e8bd85ee37b2cc916412e720988440b2aa"], + "exploitClasses": ["reentrancy"], + "tldr": "Attacker deployed a malicious ERC20 token whose transferFrom callback re-entered ExchangeWithAtomic's depositAsset, inflating their recorded balance without actually transferring funds, then withdrew ~$2.9M in USDT (and WETH) from Orion Protocol on Ethereum and BSC.", + "coreContradiction": "The swap router credited a deposit based on post-transfer balance deltas, but the malicious token's callback re-entered depositAsset before the original swap settled, doubling the recorded balance at zero cost.", + "attackSteps": [ + { + "order": 1, + "label": "Deploy malicious token & pool", + "detail": "Attacker deployed a fake ERC20 (ATK) on Ethereum whose transferFrom function contains a re-entrancy hook, and created Orion pool pairs USDT/ATK and USDC/ATK.", + "sourceIds": ["slowmist-orion", "numen-orion"] + }, + { + "order": 2, + "label": "Seed small deposit", + "detail": "Attacker deposited 0.5 USDC into the ExchangeWithAtomic contract to establish an initial balance entry.", + "sourceIds": ["slowmist-orion"] + }, + { + "order": 3, + "label": "Flash-loan USDT via Uniswap V2", + "detail": "Attacker borrowed ~2,844,766 USDT from a Uniswap V2 pair using a flash swap, providing funds for the swap path.", + "sourceIds": ["numen-orion", "slowmist-orion"] + }, + { + "order": 4, + "label": "Trigger doSwapThroughOrionPool with malicious path", + "detail": "Called ExchangeWithAtomic.doSwapThroughOrionPool with the path USDC → ATK → USDT; the contract transferred ATK tokens through the attacker-controlled malicious ERC20.", + "sourceIds": ["numen-orion", "slowmist-orion"] + }, + { + "order": 5, + "label": "Re-enter depositAsset during ATK transferFrom", + "detail": "During the ATK transfer callback, the malicious token called depositAsset again, causing the exchange contract to record an additional 2,844,700 USDT deposit before the outer swap completed, inflating the attacker's ledger balance to ~5,689,532 USDT.", + "sourceIds": ["slowmist-orion", "numen-orion", "hackmd-orion"] + }, + { + "order": 6, + "label": "Withdraw inflated balance & repay flash loan", + "detail": "Attacker withdrew 5,689,532 USDT from the victim contract, repaid the 2,844,766 USDT flash loan, and swapped the remaining profit into ~1,651 WETH via Uniswap V3.", + "sourceIds": ["slowmist-orion", "hackmd-orion"] + }, + { + "order": 7, + "label": "Launder via Tornado Cash", + "detail": "Attacker self-destructed the exploit contract and laundered proceeds through 11 × 100 ETH deposits into Tornado Cash.", + "sourceIds": ["hackmd-orion"] + } + ], + "entities": [ + { + "id": "attacker-eoa", + "role": "attacker-eoa", + "address": "0x837962b686Fd5A407fb4e5f92E8Be86A230484Bd", + "label": "Orion Protocol Exploiter EOA" + }, + { + "id": "attacker-contract", + "role": "attacker-contract", + "address": "0x5061F7e6dfc1a867D945d0ec39Ea2A33f772380A", + "label": "Attacker exploit contract (self-destructed)" + }, + { + "id": "malicious-token", + "role": "token", + "address": "0x64Acd987A8603EeAF1ee8E87Addd512908599Aec", + "label": "Malicious ATK token (Ethereum)" + }, + { + "id": "victim-exchange", + "role": "victim-contract", + "address": "0xb5599f568D3f3e6113B286d010d2BCa40A7745AA", + "label": "Orion ExchangeWithAtomic (Ethereum)" + } + ], + "fundFlow": [ + { + "fromEntityId": "victim-exchange", + "toEntityId": "attacker-eoa", + "tokenSymbol": "WETH", + "amountHuman": "~1,651 WETH", + "note": "Profit after repaying Uniswap flash loan; ~$2.84M on Ethereum" + } + ], + "sources": [ + { + "id": "slowmist-orion", + "kind": "forensic-thread", + "url": "https://slowmist.medium.com/an-analysis-of-the-attack-on-orion-protocol-c7aef70aff83", + "publisher": "SlowMist" + }, + { + "id": "numen-orion", + "kind": "forensic-thread", + "url": "https://www.numencyber.com/analysis-of-orionprotocol-reentrancy-attack-with-poc/", + "publisher": "Numen Cyber Labs" + }, + { + "id": "hackmd-orion", + "kind": "forensic-thread", + "url": "https://hackmd.io/@yOzPcv5lQjCfGsgDW-f82A/SJBnGagi2", + "publisher": "HackMD (community forensic)" + }, + { + "id": "rekt-orion", + "kind": "post-mortem", + "url": "https://rekt.news/orion-protocol-rekt", + "publisher": "rekt.news" + } + ] +} diff --git a/src/utils/hack-analysis/incidents/penpie-2024-09.json b/src/utils/hack-analysis/incidents/penpie-2024-09.json new file mode 100644 index 0000000..161ee93 --- /dev/null +++ b/src/utils/hack-analysis/incidents/penpie-2024-09.json @@ -0,0 +1,40 @@ +{ + "id": "penpie-2024-09", + "name": "Penpie Finance Reentrancy Drain", + "chain": "ethereum", + "date": "2024-09-03", + "protocol": "Penpie Finance", + "amountUsd": 27000000, + "canonicalTxs": [ + "0x663b55a1ee992603f7636ef23ff5cf19d3b261ab81494d06e218c86482df5342", + "0x42b2ec27c732100dd9037c76da415e10329ea41598de453bb0c0c9ea7ce0d8e5", + "0x56e09abb35ff12271fdb38ff8a23e4d4a7396844426a94c4d3af2e8b7a0a2813" + ], + "exploitClasses": ["reentrancy", "math-invariant-manipulation"], + "tldr": "Penpie lost ~$27M when an attacker registered a fake Pendle market whose SY token re-entered MasterPenpie's depositMarket during batchHarvestMarketRewards, inflating share balance mid-harvest to siphon rewards in agETH, rsETH and other Pendle PT/YT tokens.", + "coreContradiction": "MasterPenpie allowed permissionless Pendle market registration and did not guard batchHarvestMarketRewards against reentrancy, so an attacker-controlled SY transfer hook could mutate staking shares inside the very loop that was measuring them.", + "attackSteps": [ + { "order": 1, "label": "Deploy malicious SY + market", "detail": "Attacker deployed a fake Pendle SY token, used Pendle's permissionless createYieldContract and createNewMarket in PendleMarketFactoryV3 to mint PT/YT backed by the fake SY, then called registerPenpiePool on Penpie's Staking proxy so MasterPenpie would recognise the rogue market.", "sourceIds": ["slowmist-penpie", "rekt-penpie"] }, + { "order": 2, "label": "Borrow seed liquidity via flashloan", "detail": "Attacker flash-borrowed agETH and rswETH from Balancer Vault to seed real Pendle markets and the fake market, then deposited Pendle LP into MasterPenpie as the sole depositor.", "sourceIds": ["slowmist-penpie", "threesigma-penpie"] }, + { "order": 3, "label": "Reenter depositMarket during harvest", "detail": "Calling batchHarvestMarketRewards on MasterPenpie routed a claimRewards call through the fake SY; the attacker-controlled SY transfer callback re-entered PendleStaking.depositMarket on a legitimate Pendle market, inflating the attacker's tracked share balance before the outer harvest loop snapshotted rewards.", "sourceIds": ["slowmist-penpie", "halborn-penpie", "threesigma-penpie"] }, + { "order": 4, "label": "Claim disproportionate rewards", "detail": "Because the harvest accounted rewards proportional to the now-inflated share balance, multiclaim paid out ~$27M worth of agETH, rsETH, sUSDe and Pendle PT/YT tokens to the attacker across three exploit transactions on Ethereum mainnet.", "sourceIds": ["rekt-penpie", "threesigma-penpie"] }, + { "order": 5, "label": "Launder through Tornado Cash", "detail": "Attacker refused the whitehat bounty, bridged proceeds to ETH and began washing funds through Tornado Cash rather than returning them.", "sourceIds": ["halborn-penpie", "rekt-penpie"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x7A2f4D625Fb21F5e51562cE8Dc2E722e12A61d1B", "label": "Penpiexyz Exploiter (EOA)" }, + { "id": "attacker-contract", "role": "attacker-contract", "address": "0xCDE2cD6AEAaF0238F4cE33295be13704E4a97dE2", "label": "Exploit contract (fake SY / entry)" }, + { "id": "master-penpie", "role": "victim-contract", "address": "0x16296859C15289731521F199F0a5f762dF6347d0", "label": "MasterPenpie (reward proxy)" }, + { "id": "penpie-staking", "role": "victim-contract", "address": "0x6E799758CEE75DAe3d84e09D40dc416eCf713652", "label": "PendleStaking / Penpie Staking proxy" }, + { "id": "pendle-router", "role": "router", "address": "0x888888888889758F76e7103c6CbF23ABbF58F946", "label": "Pendle Router V4" } + ], + "fundFlow": [ + { "fromEntityId": "master-penpie", "toEntityId": "attacker-contract", "tokenSymbol": null, "amountHuman": "~$27M in agETH, rsETH, sUSDe, Pendle PT/YT", "note": "paid out via multiclaim against inflated shares" }, + { "fromEntityId": "attacker-contract", "toEntityId": "attacker-eoa", "tokenSymbol": "ETH", "amountHuman": null, "note": "consolidated proceeds sent to exploiter EOA before Tornado Cash laundering" } + ], + "sources": [ + { "id": "rekt-penpie", "kind": "forensic-thread", "url": "https://rekt.news/penpie-rekt/", "publisher": "rekt.news" }, + { "id": "slowmist-penpie", "kind": "forensic-thread", "url": "https://slowmist.medium.com/slowmist-incident-analysis-penpie-hack-e6157975898f", "publisher": "SlowMist" }, + { "id": "halborn-penpie", "kind": "post-mortem", "url": "https://www.halborn.com/blog/post/explained-the-penpie-hack-september-2024", "publisher": "Halborn" }, + { "id": "threesigma-penpie", "kind": "post-mortem", "url": "https://threesigma.xyz/blog/exploit/penpie-reentrancy-exploit-analysis", "publisher": "Three Sigma" } + ] +} diff --git a/src/utils/hack-analysis/incidents/platypus-2023-02.json b/src/utils/hack-analysis/incidents/platypus-2023-02.json new file mode 100644 index 0000000..7d16610 --- /dev/null +++ b/src/utils/hack-analysis/incidents/platypus-2023-02.json @@ -0,0 +1,39 @@ +{ + "id": "platypus-2023-02", + "name": "Platypus Finance emergencyWithdraw Exploit", + "chain": "avalanche", + "date": "2023-02-16", + "protocol": "Platypus Finance", + "amountUsd": 8500000, + "canonicalTxs": ["0x1266a937c2ccd970e5d7929021eed3ec593a95c68a99b4920c2efa226679b430"], + "exploitClasses": ["math-invariant-manipulation", "flashloan-price-manipulation"], + "tldr": "An attacker used a $44M AAVE flash loan to deposit USDC as collateral, borrow the maximum USP stablecoin against it, then call emergencyWithdraw to retrieve the collateral without repaying the debt — draining ~$8.5M because the solvency check in MasterPlatypusV4 failed to account for outstanding borrows.", + "coreContradiction": "emergencyWithdraw verified collateral sufficiency but never checked whether the user still held an outstanding loan, so a fully-leveraged borrower could exit with both the collateral and the borrowed funds.", + "attackSteps": [ + { "order": 1, "label": "Flash-loan 44M USDC", "detail": "Attacker contract borrowed 44,000,000 USDC from AAVE v3 on Avalanche in a single flash loan.", "sourceIds": ["immunefi-platypus", "numen-platypus"] }, + { "order": 2, "label": "Deposit to Platypus pool", "detail": "Deposited the 44M USDC into the Platypus USDC pool, receiving an equivalent amount of LP-USDC tokens.", "sourceIds": ["immunefi-platypus", "numen-platypus"] }, + { "order": 3, "label": "Stake LP as collateral", "detail": "Staked LP-USDC in MasterPlatypusV4 via the deposit function, registering the position as collateral eligible for USP borrowing.", "sourceIds": ["immunefi-platypus"] }, + { "order": 4, "label": "Borrow max USP", "detail": "Called PlatypusTreasure to borrow ~41.8M USP (95% of staked collateral value), the protocol maximum.", "sourceIds": ["immunefi-platypus", "numen-platypus"] }, + { "order": 5, "label": "emergencyWithdraw LP without repaying", "detail": "Called MasterPlatypusV4.emergencyWithdraw(); the broken solvency check only confirmed collateral value against the 95% cap but did not verify whether a loan existed — so it returned all LP tokens while the USP debt remained outstanding.", "sourceIds": ["immunefi-platypus", "numen-platypus"] }, + { "order": 6, "label": "Redeem USDC, repay flash loan", "detail": "Used recovered LP-USDC to withdraw the original 44M USDC from the Platypus pool, then repaid the AAVE flash loan, keeping the USP as profit.", "sourceIds": ["immunefi-platypus"] }, + { "order": 7, "label": "Swap USP for stablecoins", "detail": "Swapped the 41M+ USP against existing stablecoin liquidity in Platypus pools, extracting approximately $8.5M in real stablecoins before the protocol could pause.", "sourceIds": ["immunefi-platypus", "numen-platypus"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0xefF003D64046A6f521BA31f39405cb720E953958", "label": "Attacker EOA" }, + { "id": "attacker-contract", "role": "attacker-contract", "address": "0x67AfDD6489D40a01DaE65f709367E1b1D18a5322", "label": "Exploit contract" }, + { "id": "victim-masterplatypus", "role": "victim-contract", "address": "0xc007f27B757A782c833C568f5851Ae1DFE0e6ec7", "label": "MasterPlatypusV4" }, + { "id": "victim-proxy", "role": "proxy", "address": "0xfF6934aAC9C94E1C39358D4fDCF70aeca77D0AB0", "label": "MasterPlatypusV4 proxy" }, + { "id": "victim-treasure", "role": "victim-contract", "address": "0xBCD6796177aB8071F6a9ba2C3e2E0301Ee91BEf5", "label": "PlatypusTreasure (USP minter)" }, + { "id": "victim-pool", "role": "pool", "address": "0x66357dCaCe80431aee0A7507e2E361B7e2402370", "label": "Platypus USDC pool" } + ], + "fundFlow": [ + { "fromEntityId": "victim-pool", "toEntityId": "attacker-contract", "tokenSymbol": "USDC", "amountHuman": "44,000,000 USDC", "note": "flash loan from AAVE, routed through pool LP position" }, + { "fromEntityId": "victim-treasure", "toEntityId": "attacker-contract", "tokenSymbol": "USP", "amountHuman": "~41,794,533 USP", "note": "borrowed against LP collateral" }, + { "fromEntityId": "victim-pool", "toEntityId": "attacker-eoa", "tokenSymbol": "USDC", "amountHuman": "~8,500,000 USDC", "note": "net profit after repaying flash loan — extracted by swapping USP into pool stablecoins" } + ], + "sources": [ + { "id": "immunefi-platypus", "kind": "forensic-thread", "url": "https://immunefi.com/blog/industry-trends/platypus-finance-hack-analysis/", "publisher": "Immunefi" }, + { "id": "numen-platypus", "kind": "forensic-thread", "url": "https://www.numencyber.com/platypus-finance-project-hit-by-8-5m-flash-loan-attack/", "publisher": "Numen Cyber" }, + { "id": "dn-platypus", "kind": "database-row", "url": "https://dn.institute/research/cyberattacks/incidents/2023-02-16-platypus/", "publisher": "Distributed Networks Institute" } + ] +} diff --git a/src/utils/hack-analysis/incidents/qubit-2022-01.json b/src/utils/hack-analysis/incidents/qubit-2022-01.json new file mode 100644 index 0000000..561e1db --- /dev/null +++ b/src/utils/hack-analysis/incidents/qubit-2022-01.json @@ -0,0 +1,42 @@ +{ + "id": "qubit-2022-01", + "name": "Qubit Finance QBridge Exploit", + "chain": "bsc", + "date": "2022-01-27", + "protocol": "Qubit Finance / QBridge", + "amountUsd": 80000000, + "canonicalTxs": [ + "0x478d83f2ad909c64a9a3d807b3d8399bb67a997f9721fc5580ae2c51fab92acf", + "0x33628dcc2ca6cd89a96d241bdf17cdc8785cf4322dcaf2c79766c990579aea02" + ], + "exploitClasses": ["bridge-message-forgery", "access-control-bypass"], + "tldr": "Qubit's QBridge accepted a zero-value deposit() call on Ethereum as if real ETH had been escrowed; BSC relayers then minted 77,162 qXETH to the attacker, who drained ~206,809 BNB (~$80M) from Qubit's lending pool.", + "coreContradiction": "The legacy deposit() path forwarded any ERC20-style payload to the handler without enforcing msg.value, so a WETH resourceID plus crafted data emitted a canonical Deposit event for ETH that was never delivered.", + "attackSteps": [ + { "order": 1, "label": "Zero-value deposit on Ethereum", "detail": "Attacker EOA called QBridge.deposit(destinationChainID=1, WETH resourceID, data) on the Ethereum QBridge with 0 ETH attached; the legacy deposit function dispatched to the ETH handler and emitted a valid Deposit event despite no ETH being escrowed.", "sourceIds": ["halborn-qubit", "certik-qubit", "slowmist-qubit"] }, + { "order": 2, "label": "Relayer votes proposal on BSC", "detail": "Qubit's off-chain relayer observed the Ethereum Deposit event and submitted voteProposal on the BSC QBridge, which on quorum executed the handler and minted qXETH on BSC to the attacker.", "sourceIds": ["slowmist-qubit", "qubit-postmortem"] }, + { "order": 3, "label": "Repeat to accumulate qXETH", "detail": "Attacker repeated the zero-value deposit flow across multiple transactions, accumulating ~77,162 qXETH on BSC without ever depositing ETH on Ethereum.", "sourceIds": ["certik-qubit", "merkle-qubit"] }, + { "order": 4, "label": "Borrow against fake collateral and drain", "detail": "Using the forged qXETH as collateral in Qubit's lending pools, the attacker borrowed the protocol's entire available liquidity, exiting with ~206,809 BNB and other assets worth ~$80M.", "sourceIds": ["certik-qubit", "merkle-qubit", "qubit-postmortem"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0xD01Ae1A708614948B2B5e0B7AB5be6AFA01325c7", "label": "QubitFin Exploiter EOA" }, + { "id": "qbridge-eth", "role": "bridge", "address": "0x20E5E35ba29dC3B540a1aee781D0814D5c77Bce6", "label": "QBridge proxy (Ethereum, deposit entrypoint)" }, + { "id": "qbridge-bsc", "role": "bridge", "address": "0x4d8aE68fCAe98Bf93299548545933c0D273BA23a", "label": "QBridge proxy (BSC, voteProposal target)" }, + { "id": "qbridge-bsc-impl", "role": "implementation", "address": "0xD88E328c305f541e2De6D3c85ed081653cd8A726", "label": "QBridge implementation (BSC)" }, + { "id": "qxeth-token", "role": "token", "address": "0xfD7A5506F434f5334C100EFb765025243C39137C", "label": "qXETH token proxy (BSC)" }, + { "id": "qxeth-impl", "role": "implementation", "address": "0x02971e8d3a32C0c76beA97c1D18967b9AFbefa69", "label": "QTokenExploited implementation (qXETH)" }, + { "id": "qubit-pool", "role": "pool", "address": "0xbE1B5D17777565D67A5D2793f879aBF59Ae5D351", "label": "Qubit lending pool (borrow target on BSC)" } + ], + "fundFlow": [ + { "fromEntityId": "attacker-eoa", "toEntityId": "qbridge-eth", "tokenSymbol": "ETH", "amountHuman": "0 ETH", "note": "zero-value deposit() call that emitted a Deposit event for WETH" }, + { "fromEntityId": "qxeth-token", "toEntityId": "attacker-eoa", "tokenSymbol": "qXETH", "amountHuman": "~77,162 qXETH", "note": "minted on BSC after relayers voted the forged proposal" }, + { "fromEntityId": "qubit-pool", "toEntityId": "attacker-eoa", "tokenSymbol": "BNB", "amountHuman": "~206,809 BNB", "note": "borrowed against fake qXETH collateral; ~$80M total drained" } + ], + "sources": [ + { "id": "qubit-postmortem", "kind": "post-mortem", "url": "https://medium.com/@QubitFin/protocol-exploit-report-2-30aade4d66de", "publisher": "Qubit Finance" }, + { "id": "certik-qubit", "kind": "forensic-thread", "url": "https://certik.medium.com/qubit-bridge-collapse-exploited-to-the-tune-of-80-million-a7ab9068e1a0", "publisher": "CertiK" }, + { "id": "slowmist-qubit", "kind": "forensic-thread", "url": "https://slowmist.medium.com/our-analysis-of-the-80m-qubit-finance-exploit-b0f272cd8c25", "publisher": "SlowMist" }, + { "id": "halborn-qubit", "kind": "forensic-thread", "url": "https://www.halborn.com/blog/post/explained-the-qubit-hack-january-2022", "publisher": "Halborn" }, + { "id": "merkle-qubit", "kind": "forensic-thread", "url": "https://www.merklescience.com/blog/hack-track-analysis-of-qubit-finance-exploit", "publisher": "Merkle Science" } + ] +} diff --git a/src/utils/hack-analysis/incidents/radiant-2024-10.json b/src/utils/hack-analysis/incidents/radiant-2024-10.json new file mode 100644 index 0000000..4acd1a4 --- /dev/null +++ b/src/utils/hack-analysis/incidents/radiant-2024-10.json @@ -0,0 +1,42 @@ +{ + "id": "radiant-2024-10", + "name": "Radiant Capital Multisig Compromise", + "chain": "arbitrum", + "date": "2024-10-16", + "protocol": "Radiant Capital", + "amountUsd": 58000000, + "canonicalTxs": [ + "0x7856552db409fe51e17339ab1e0e1ce9c85d68bf0f4de4c110fc4e372ea02fb1", + "0xd97b93f633aee356d992b49193e60a571b8c466bf46aaf072368f975dc11841c" + ], + "exploitClasses": ["signer-compromise", "delegatecall-to-user-controlled"], + "tldr": "Radiant Capital lost ~$58M when DPRK-linked UNC4736 compromised three developer machines with INLETDRIFT malware, spoofed hardware-wallet signing UIs, and collected legitimate 3-of-11 multisig signatures for an upgradeToAndCall that delegatecalled a pre-deployed malicious LendingPool implementation. The attack was cross-chain, draining reserves on both Arbitrum and BSC in parallel; Arbitrum is recorded as the canonical chain here and the BSC drain tx is included alongside the Arbitrum tx in canonicalTxs.", + "coreContradiction": "Signers saw a benign Gnosis Safe transaction in their hardware-wallet and Tenderly previews while the on-wire calldata actually transferred LendingPoolAddressesProvider ownership to an attacker contract and delegatecalled a poisoned implementation.", + "attackSteps": [ + { "order": 1, "label": "INLETDRIFT implant", "detail": "A Radiant contributor received a Telegram DM impersonating a former contractor with a zipped macOS app; opening it installed the INLETDRIFT backdoor, which then spread to two other developer machines weeks before the heist.", "sourceIds": ["radiant-postmortem", "radiant-update"] }, + { "order": 2, "label": "Malicious impl pre-staged", "detail": "The attacker pre-deployed an identical malicious contract at 0x57ba89...fbf5 on Arbitrum, BSC, Ethereum and Base roughly 14 days before the attack to serve as the upgrade target.", "sourceIds": ["halborn-radiant", "quill-radiant"] }, + { "order": 3, "label": "Signer UI spoofing", "detail": "The malware intercepted Safe transaction flows on the compromised boxes: hardware wallets and Tenderly showed a routine transfer while the real calldata executed transferOwnership of LendingPoolAddressesProvider and upgraded the LendingPool implementation.", "sourceIds": ["radiant-postmortem", "halborn-radiant"] }, + { "order": 4, "label": "Legitimate 3-of-11 signatures collected", "detail": "Three compromised signers produced valid signatures for what looked like an expected Safe operation, satisfying the 3-of-11 threshold on the Arbitrum admin multisig at 0x111C...2177.", "sourceIds": ["radiant-postmortem", "radiant-update"] }, + { "order": 5, "label": "Upgrade + drain on Arbitrum and BSC", "detail": "Execution at 17:09:18 UTC on Arbitrum (tx 0x7856...2fb1) and ~80 seconds later on BSC (tx 0xd97b...841c) upgraded the pools via delegatecall and drained USDC, USDT, ARB, WBTC and WETH reserves to the attacker EOA at 0x0629...8962, roughly $58M total.", "sourceIds": ["halborn-radiant", "quill-radiant", "radiant-update"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x0629b1048298AE9deff0F4100A31967Fb3f98962", "label": "Radiant exploiter EOA" }, + { "id": "attacker-impl", "role": "attacker-contract", "address": "0x57ba8957ed2ff2e7AE38F4935451E81Ce1eEFbf5", "label": "Malicious LendingPool implementation / multicall" }, + { "id": "lending-pool", "role": "victim-contract", "address": "0xE23B4AE3624fB6f7cDEF29bC8EAD912f1Ede6886", "label": "Radiant LendingPool proxy (Arbitrum)" }, + { "id": "addresses-provider", "role": "proxy", "address": "0x454a8dAf74B24037eE2fa073Ce1be9277Ed6160a", "label": "LendingPoolAddressesProvider (Arbitrum)" }, + { "id": "radiant-multisig", "role": "signer", "address": "0x111CEEee040739fD91D29C34C33E6B3E112F2177", "label": "Radiant 3-of-11 admin Safe (Arbitrum)" }, + { "id": "compromised-dev-1", "role": "signer", "address": "0x20340c2a71055FD2887D9A71054100FF7F425BE5", "label": "Compromised dev signer (Ledger via Rabby)" }, + { "id": "compromised-dev-2", "role": "signer", "address": "0x83434627e72d977af18F8D2F26203895050eF9Ce", "label": "Compromised dev signer (Ledger via Rabby)" }, + { "id": "compromised-dev-3", "role": "signer", "address": "0xbB67c265e7197A7c3Cd458F8F7C1d79a2fb04d57", "label": "Compromised dev signer (Trezor via Frame)" } + ], + "fundFlow": [ + { "fromEntityId": "lending-pool", "toEntityId": "attacker-impl", "tokenSymbol": null, "amountHuman": null, "note": "upgradeToAndCall delegatecalled the malicious implementation inside the LendingPool proxy's storage context" }, + { "fromEntityId": "lending-pool", "toEntityId": "attacker-eoa", "tokenSymbol": null, "amountHuman": "~$58M mixed (USDC, USDT, ARB, WBTC, WETH)", "note": "drained reserves across Arbitrum and BSC to attacker EOA" } + ], + "sources": [ + { "id": "radiant-postmortem", "kind": "post-mortem", "url": "https://medium.com/@RadiantCapital/radiant-post-mortem-fecd6cd38081", "publisher": "Radiant Capital" }, + { "id": "radiant-update", "kind": "official", "url": "https://medium.com/@RadiantCapital/radiant-capital-incident-update-e56d8c23829e", "publisher": "Radiant Capital" }, + { "id": "halborn-radiant", "kind": "forensic-thread", "url": "https://www.halborn.com/blog/post/explained-the-radiant-capital-hack-october-2024", "publisher": "Halborn" }, + { "id": "quill-radiant", "kind": "forensic-thread", "url": "https://www.quillaudits.com/blog/hack-analysis/radiant-capital-58million-hack", "publisher": "QuillAudits" } + ] +} diff --git a/src/utils/hack-analysis/incidents/rari-fuse-2022-04.json b/src/utils/hack-analysis/incidents/rari-fuse-2022-04.json new file mode 100644 index 0000000..0394ff2 --- /dev/null +++ b/src/utils/hack-analysis/incidents/rari-fuse-2022-04.json @@ -0,0 +1,50 @@ +{ + "id": "rari-fuse-2022-04", + "name": "Rari Capital Fuse Pools Reentrancy Drain", + "chain": "ethereum", + "date": "2022-04-30", + "protocol": "Rari Capital Fuse", + "amountUsd": 80000000, + "canonicalTxs": [ + "0xab486012f21be741c9e674ffda227e30518e8a1e37a5f1d58d0b0d41f6e76530", + "0xadbe5cf9269a001d50990d0c29075b402bcc3a0b0f3258821881621b787b35c6" + ], + "exploitClasses": ["reentrancy"], + "tldr": "Seven Rari Fuse lending pools (8, 18, 27, 127, 144, 146, 156) were drained of ~$80M when the attacker re-entered the unprotected Comptroller.exitMarket() during a native-ETH borrow callback, exiting collateral while a debt remained open.", + "coreContradiction": "Fuse's CEther.borrow() sent ETH via .call.value() before finalizing account state, and Comptroller.exitMarket() was missing from the reentrancy-lock whitelist — so a borrower's fallback could legally withdraw the same collateral that was backing the fresh debt.", + "attackSteps": [ + { "order": 1, "label": "Flash-loan liquidity", "detail": "Attacker contract 0x3207...6C45 took a Balancer vault flash loan (~50,000 WETH and ~80,000 wstETH) to seed collateral for each targeted Fuse pool.", "sourceIds": ["blockapex-rari", "hackmd-raddy"] }, + { "order": 2, "label": "Supply and enter market", "detail": "The flash-loaned assets were deposited into the victim fToken (e.g. fwstETH in pool 146, fFEI in pool 8) and enterMarkets() was called so the deposit counted as collateral.", "sourceIds": ["blockapex-rari", "solidityscan-rari"] }, + { "order": 3, "label": "Borrow native ETH to trigger callback", "detail": "Attacker called borrow() on the pool's CEther fToken. Fuse transferred ETH to the attacker contract via low-level .call.value() BEFORE updating the borrower's collateral accounting.", "sourceIds": ["solidityscan-rari", "rekt-rari"] }, + { "order": 4, "label": "Reenter exitMarket in fallback", "detail": "Inside the receive() fallback, the attacker re-entered Comptroller.exitMarket() — a function omitted from Fuse's reentrancy-lock set — which released the still-posted collateral despite the freshly minted debt.", "sourceIds": ["solidityscan-rari", "blockapex-rari", "rekt-rari"] }, + { "order": 5, "label": "Redeem collateral and repeat", "detail": "Attacker redeemed the now-unlocked fTokens for underlying, repaid the Balancer flash loan, and repeated the sequence across pools 8, 18, 27, 127, 144, 146, and 156 for a cumulative ~$80M.", "sourceIds": ["coindesk-rari", "rekt-rari", "hackmd-raddy"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x6162759eDAd730152F0dF8115c698a42E666157F", "label": "FeiProtocol-Fuse Exploiter EOA" }, + { "id": "attacker-contract", "role": "attacker-contract", "address": "0x32075bAd9050d4767018084F0Cb87b3182D36C45", "label": "Exploit contract (flash-loan + reentrancy)" }, + { "id": "attacker-receiver", "role": "attacker-contract", "address": "0xE39f3C40966DF56c69AA508D8AD459E77B8a2bc1", "label": "Attacker profit receiver contract" }, + { "id": "fuse-pool8-comptroller", "role": "victim-contract", "address": "0xc54172e34046c1653d1920d40333Dd358c7a1aF4", "label": "Fuse Pool 8 Comptroller" }, + { "id": "fuse-pool8-fei", "role": "victim-contract", "address": "0xd8553552f8868C1Ef160eEdf031cF0BCf9686945", "label": "Fuse Pool 8 fFEI" }, + { "id": "fuse-pool146-comptroller", "role": "victim-contract", "address": "0x88F7c23EA6C4C404dA463Bc9aE03b012B32DEf9e", "label": "Fuse Pool 146 Comptroller" }, + { "id": "fuse-pool146-feth", "role": "victim-contract", "address": "0xfbD8Aaf46Ab3C2732FA930e5B343cd67cEA5054C", "label": "Fuse Pool 146 fETH (CEther)" }, + { "id": "fuse-pool146-fwsteth", "role": "victim-contract", "address": "0x49dA42a1EcA4AC6cA0C6943d9E5dc64e4641e0E3", "label": "Fuse Pool 146 fwstETH" }, + { "id": "fuse-pool127-fusdc", "role": "victim-contract", "address": "0xEbE0d1cb6A0b8569929e062d67bfbC07608f0A47", "label": "Fuse Pool 127 fUSDC" }, + { "id": "fuse-pool127-feth", "role": "victim-contract", "address": "0x26267e41CeCa7C8E0f143554Af707336f27Fa051", "label": "Fuse Pool 127 fETH (CEther)" }, + { "id": "fuse-pool127-comptroller", "role": "victim-contract", "address": "0x3f2D1BC6D02522dbcdb216b2e75eDDdAFE04B16F", "label": "Fuse Pool 127 Comptroller" }, + { "id": "balancer-vault", "role": "pool", "address": "0xBA12222222228d8Ba445958a75a0704d566BF2C8", "label": "Balancer V2 Vault (flash-loan source)" } + ], + "fundFlow": [ + { "fromEntityId": "balancer-vault", "toEntityId": "attacker-contract", "tokenSymbol": "WETH", "amountHuman": "~50,000 WETH + ~80,000 wstETH", "note": "flash loan later repaid" }, + { "fromEntityId": "fuse-pool146-feth", "toEntityId": "attacker-contract", "tokenSymbol": "ETH", "amountHuman": null, "note": "borrow() transferred ETH via .call.value() triggering fallback" }, + { "fromEntityId": "fuse-pool8-fei", "toEntityId": "attacker-contract", "tokenSymbol": "FEI", "amountHuman": null, "note": "collateral redeemed after exitMarket reentrancy" }, + { "fromEntityId": "fuse-pool127-fusdc", "toEntityId": "attacker-contract", "tokenSymbol": "USDC", "amountHuman": null, "note": "collateral redeemed after exitMarket reentrancy" }, + { "fromEntityId": "attacker-contract", "toEntityId": "attacker-eoa", "tokenSymbol": null, "amountHuman": "~$80,000,000 across 7 pools", "note": "net proceeds across pools 8/18/27/127/144/146/156" } + ], + "sources": [ + { "id": "rekt-rari", "kind": "post-mortem", "url": "https://rekt.news/rari-rekt/", "publisher": "rekt.news" }, + { "id": "coindesk-rari", "kind": "database-row", "url": "https://www.coindesk.com/business/2022/04/30/defi-lender-rari-capitalfei-loses-80m-in-hack", "publisher": "CoinDesk" }, + { "id": "blockapex-rari", "kind": "forensic-thread", "url": "https://medium.com/blockapex/rari-capital-hack-analysis-poc-3f0328e555d9", "publisher": "BlockApex" }, + { "id": "solidityscan-rari", "kind": "forensic-thread", "url": "https://blog.solidityscan.com/rari-capital-re-entrancy-vulnerability-analysis-25df2bbfc803", "publisher": "SolidityScan" }, + { "id": "hackmd-raddy", "kind": "forensic-thread", "url": "https://hackmd.io/@raddy/H1H8uWASc", "publisher": "raddy (HackMD)" } + ] +} diff --git a/src/utils/hack-analysis/incidents/ronin-2022-03.json b/src/utils/hack-analysis/incidents/ronin-2022-03.json new file mode 100644 index 0000000..df3a37c --- /dev/null +++ b/src/utils/hack-analysis/incidents/ronin-2022-03.json @@ -0,0 +1,39 @@ +{ + "id": "ronin-2022-03", + "name": "Ronin Bridge Validator Compromise", + "chain": "ethereum", + "date": "2022-03-23", + "protocol": "Ronin Network / Axie Infinity", + "amountUsd": 624000000, + "canonicalTxs": [ + "0xc28fad5e8d5e0ce6a2eaf67b6687be5d58113e16be590824d6cfa1a94467d0b7", + "0xed2c72ef1a552ddaec6dd1f5cddf0b59a8f37f82bdda5257d9c7c37db7bb9b08" + ], + "exploitClasses": ["signer-compromise"], + "tldr": "Lazarus Group (DPRK) compromised 5 of 9 Ronin validator private keys via spear-phishing and an un-revoked gas-free RPC backdoor, then forged two withdrawalERC20For signatures to drain 173,600 ETH and 25.5M USDC from the Ethereum-side Ronin Bridge contract.", + "coreContradiction": "The bridge's 5-of-9 validator threshold was designed as a safety mechanism, but a never-revoked November 2021 arrangement allowed one Axie DAO validator key to be used via an RPC backdoor — giving the attacker majority control with only four directly compromised Sky Mavis keys.", + "attackSteps": [ + { "order": 1, "label": "Spear-phish Sky Mavis employee", "detail": "Lazarus sent fake job-offer documents to a Sky Mavis engineer; malware harvested credentials and gave initial access to internal infrastructure.", "sourceIds": ["ronin-postmortem", "rekt-ronin"] }, + { "order": 2, "label": "Compromise 4 Sky Mavis validator keys", "detail": "Using the foothold, attackers pivoted through Sky Mavis infrastructure and extracted private keys for four of its controlled Ronin validator nodes.", "sourceIds": ["ronin-postmortem"] }, + { "order": 3, "label": "Abuse un-revoked Axie DAO RPC backdoor", "detail": "In November 2021 Sky Mavis had been whitelisted to sign on behalf of the Axie DAO validator via a gas-free RPC node; this whitelist was never removed, letting the attacker harvest a fifth signature.", "sourceIds": ["rekt-ronin", "ronin-postmortem"] }, + { "order": 4, "label": "Forge withdrawERC20For — 173,600 ETH", "detail": "With 5 valid validator signatures, the attacker called withdrawERC20For on the Ethereum-side Ronin Bridge contract (block 14442835) and received 173,600 WETH.", "sourceIds": ["rekt-ronin", "elliptic-ronin"] }, + { "order": 5, "label": "Forge withdrawERC20For — 25.5M USDC", "detail": "Two blocks later (block 14442840) a second signed withdrawal drained 25,500,000 USDC to the same attacker address.", "sourceIds": ["elliptic-ronin", "chainalysis-ronin"] }, + { "order": 6, "label": "Launder via Tornado Cash and bridges", "detail": "Funds were routed through 12,000+ intermediary addresses, Tornado Cash, ETH-to-BTC cross-chain swaps, and eventually fiat cashout channels attributed to DPRK.", "sourceIds": ["chainalysis-ronin"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x098B716B8Aaf21512996dC57EB0615e2383E2f96", "label": "Ronin Bridge Exploiter (Lazarus / DPRK)" }, + { "id": "victim-bridge", "role": "victim-contract", "address": "0x1A2a1c938CE3eC39b6D47113c7955bAa9DD454F2", "label": "Axie Infinity: Ronin Bridge (Ethereum)" }, + { "id": "bridge-impl", "role": "implementation", "address": "0x8407dc57739bCDA7aA53Ca6F12F82F9d51c2F21E", "label": "MainchainGateway implementation" }, + { "id": "token-usdc", "role": "token", "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "label": "USDC (Circle)" } + ], + "fundFlow": [ + { "fromEntityId": "victim-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": "ETH", "amountHuman": "173,600 ETH", "note": "withdrawERC20For tx 0xc28f... block 14442835" }, + { "fromEntityId": "victim-bridge", "toEntityId": "attacker-eoa", "tokenSymbol": "USDC", "amountHuman": "25,500,000 USDC", "note": "withdrawERC20For tx 0xed2c... block 14442840" } + ], + "sources": [ + { "id": "ronin-postmortem", "kind": "official", "url": "https://roninchain.com/blog/posts/back-to-building-ronin-security-breach-6513cc78a5edc1001b03c364", "publisher": "Sky Mavis / Ronin Network" }, + { "id": "rekt-ronin", "kind": "post-mortem", "url": "https://rekt.news/ronin-rekt", "publisher": "rekt.news" }, + { "id": "elliptic-ronin", "kind": "forensic-thread", "url": "https://www.elliptic.co/blog/540-million-stolen-from-the-ronin-defi-bridge", "publisher": "Elliptic" }, + { "id": "chainalysis-ronin", "kind": "forensic-thread", "url": "https://www.chainalysis.com/blog/axie-infinity-ronin-bridge-dprk-hack-seizure/", "publisher": "Chainalysis" } + ] +} diff --git a/src/utils/hack-analysis/incidents/team-finance-2022-10.json b/src/utils/hack-analysis/incidents/team-finance-2022-10.json new file mode 100644 index 0000000..a504f44 --- /dev/null +++ b/src/utils/hack-analysis/incidents/team-finance-2022-10.json @@ -0,0 +1,41 @@ +{ + "id": "team-finance-2022-10", + "name": "Team Finance Liquidity Locker Migration Exploit", + "chain": "ethereum", + "date": "2022-10-27", + "protocol": "Team Finance", + "amountUsd": 14500000, + "canonicalTxs": ["0xb2e3ea72d353da43a2ac9a8f1670fd16463ab370e563b9b5b26119b2601277ce"], + "exploitClasses": ["access-control-bypass", "math-invariant-manipulation"], + "tldr": "Team Finance's liquidity locker lost ~$14.5M when an attacker abused its unvalidated Uniswap v2 to v3 migrate() function to redirect locked LP for CAW, TSUKA, KNDX, and FEG into attacker-controlled v3 positions.", + "coreContradiction": "migrate() trusted the caller to name which locked pair to migrate, so locking a throwaway token was enough to pull any other project's real LP through the Uniswap v3 migrator.", + "attackSteps": [ + { "order": 1, "label": "Lock decoy token", "detail": "Attacker contract created a worthless token, paired it on Uniswap v2, and locked that LP in the Team Finance locker to obtain a valid lock id.", "sourceIds": ["slowmist-teamfinance", "halborn-teamfinance"] }, + { "order": 2, "label": "Call migrate() with mismatched pair", "detail": "Using the decoy lock id, the attacker invoked migrate() but passed parameters pointing at a different, real v2 pair (CAW/USDC, TSUKA/USDC, KNDX/WETH, FEG/WETH); the locker never checked that the migrated pair matched the locked token.", "sourceIds": ["slowmist-teamfinance", "rekt-teamfinance"] }, + { "order": 3, "label": "Skewed v3 pool initialization", "detail": "Through the Uniswap v3 Migrator the attacker initialized the new v3 pool at a heavily skewed price, so almost the entire underlying liquidity minted as a single in-range v3 position to the attacker instead of to the original locker.", "sourceIds": ["halborn-teamfinance", "rekt-teamfinance"] }, + { "order": 4, "label": "Collect v3 NFT and cash out", "detail": "Four Uniswap v3 position NFTs were minted to the attacker contract; it burned them to withdraw the paired tokens (CAW, TSUKA, KNDX, FEG, USDC, WETH) and forwarded the proceeds to EOA 0x161c...99Fd and secondary wallet 0xBa39...7e6E.", "sourceIds": ["rekt-teamfinance", "coindesk-teamfinance"] } + ], + "entities": [ + { "id": "attacker-eoa", "role": "attacker-eoa", "address": "0x161cebB807Ac181d5303A4cCec2FC580CC5899Fd", "label": "Attacker EOA (deployer)" }, + { "id": "attacker-eoa-2", "role": "attacker-eoa", "address": "0xBa399a2580785A2dEd740F5e30EC89Fb3E617e6E", "label": "Attacker secondary EOA (stolen fund holder)" }, + { "id": "attacker-contract", "role": "attacker-contract", "address": "0xCFF07C4e6aa9E2fEc04DAaF5f41d1b10f3adAdF4", "label": "Attacker exploit contract" }, + { "id": "victim-locker", "role": "victim-contract", "address": "0xE2fE530C047f2d85298b07D9333C05737f1435fB", "label": "TrustSwap/Team Finance Lock (vulnerable migrate)" }, + { "id": "uni-v3-migrator", "role": "router", "address": "0xA5644E29708357803b5A882D272c41cC0dF92B34", "label": "Uniswap V3 Migrator" }, + { "id": "uni-v3-npm", "role": "router", "address": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", "label": "Uniswap V3 NonfungiblePositionManager" }, + { "id": "pool-caw", "role": "pool", "address": "0x7a809081f991eCfe0aB2727C7E90D2Ad7c2E411E", "label": "Uniswap V2 USDC/CAW pair" }, + { "id": "pool-tsuka", "role": "pool", "address": "0x67CeA36eEB36Ace126A3Ca6E21405258130CF33C", "label": "Uniswap V2 USDC/TSUKA pair" }, + { "id": "pool-kndx", "role": "pool", "address": "0x9267C29e4f517cE9f6d603a15B50Aa47cE32278D", "label": "Uniswap V2 KNDX pair" }, + { "id": "pool-feg", "role": "pool", "address": "0x854373387E41371Ac6E307A1F29603c6Fa10D872", "label": "Uniswap V2 FEG pair" } + ], + "fundFlow": [ + { "fromEntityId": "victim-locker", "toEntityId": "uni-v3-migrator", "tokenSymbol": null, "amountHuman": "4 LP positions", "note": "Locked v2 LP for CAW, TSUKA, KNDX, FEG handed to v3 migrator via unchecked migrate()" }, + { "fromEntityId": "uni-v3-npm", "toEntityId": "attacker-contract", "tokenSymbol": null, "amountHuman": "4 v3 position NFTs", "note": "v3 positions minted to attacker because of skewed initial price" }, + { "fromEntityId": "attacker-contract", "toEntityId": "attacker-eoa-2", "tokenSymbol": null, "amountHuman": "~$14.5M in CAW, TSUKA, KNDX, FEG, USDC, WETH", "note": "Proceeds aggregated in attacker wallet 0xBa39...7e6E; ~$7M later returned" } + ], + "sources": [ + { "id": "slowmist-teamfinance", "kind": "forensic-thread", "url": "https://slowmist.medium.com/analysis-review-of-team-finance-exploit-f439c2f63e2", "publisher": "SlowMist" }, + { "id": "halborn-teamfinance", "kind": "forensic-thread", "url": "https://www.halborn.com/blog/post/explained-the-team-finance-hack-october-2022", "publisher": "Halborn" }, + { "id": "rekt-teamfinance", "kind": "post-mortem", "url": "https://rekt.news/teamfinance-rekt", "publisher": "rekt.news" }, + { "id": "coindesk-teamfinance", "kind": "official", "url": "https://www.coindesk.com/tech/2022/10/31/attacker-behind-145m-team-finance-exploit-returns-7m", "publisher": "CoinDesk" } + ] +} diff --git a/src/utils/hack-analysis/incidents/woofi-2024-03.json b/src/utils/hack-analysis/incidents/woofi-2024-03.json new file mode 100644 index 0000000..b90e528 --- /dev/null +++ b/src/utils/hack-analysis/incidents/woofi-2024-03.json @@ -0,0 +1,118 @@ +{ + "id": "woofi-2024-03", + "name": "WOOFi sPMM Oracle Manipulation", + "chain": "arbitrum", + "date": "2024-03-05", + "protocol": "WOOFi", + "amountUsd": 8750000, + "canonicalTxs": ["0x57e555328b7def90e1fc2a0f7aa6df8d601a8f15803800a5aaf0a20382f21fbd"], + "exploitClasses": ["oracle-manipulation", "flashloan-price-manipulation"], + "tldr": "Attacker flash-loaned WOO tokens on Arbitrum to skew WOOFi's sPMM internal price to near-zero, then swapped back at the manipulated price; the Chainlink fallback oracle was never configured for WOO, leaving no safety net. Repeated three times in 13 minutes for ~$8.75M.", + "coreContradiction": "WOOFi's sPMM used an unchecked internal price state for WOO with no Chainlink fallback, so draining the pool of WOO via a flash loan collapsed the reported price to $0.00000009 — making a trivial swap yield millions.", + "attackSteps": [ + { + "order": 1, + "label": "Flash-loan USDC + WOO", + "detail": "Attacker borrowed ~10.6M USDC via a Uniswap V3 flash loan and borrowed the entire WOO reserves (~7.7M WOO) from Trader Joe and Silo Finance on Arbitrum.", + "sourceIds": ["rekt-woofi", "cyfrin-woofi"] + }, + { + "order": 2, + "label": "Sell WOO into WooPPV2", + "detail": "Selling the borrowed WOO into the WooPPV2 sPMM pool caused the internal price oracle for WOO to collapse to ~$0.00000009 due to unchecked state update math.", + "sourceIds": ["cyfrin-woofi", "woo-postmortem"] + }, + { + "order": 3, + "label": "Buy WOO at near-zero price", + "detail": "With the sPMM price at essentially zero, attacker swapped ~10M WOO back out of the pool for negligible cost, draining USDC and WETH reserves.", + "sourceIds": ["rekt-woofi", "cyfrin-woofi"] + }, + { + "order": 4, + "label": "Repay flash loans, repeat", + "detail": "Flash loans were repaid atomically; the attack was repeated two more times (three total) within 13 minutes before WOOFi paused contracts, netting ~$8.75M.", + "sourceIds": ["rekt-woofi", "woo-postmortem"] + } + ], + "entities": [ + { + "id": "attacker-eoa", + "role": "attacker-eoa", + "address": "0x9961190B258897BCa7a12B8f37F415E689D281C4", + "label": "WOOFi Exploiter EOA" + }, + { + "id": "attacker-contract", + "role": "attacker-contract", + "address": "0xD4c633C9A765bC690E1FbA566981c1F4eab52dF0", + "label": "WOOFi Exploit Contract" + }, + { + "id": "victim-pool", + "role": "victim-contract", + "address": "0xeFF23B4bE1091b53205E35f3AfCD9C7182bf3062", + "label": "WooPPV2 sPMM Pool (Arbitrum)" + }, + { + "id": "woo-oracle", + "role": "oracle", + "address": "0x73504eaCB100c7576146618DC306c97454CB3620", + "label": "WOO Oracle V2 (sPMM internal)" + }, + { + "id": "woo-token", + "role": "token", + "address": "0xcAFcD85D8ca7Ad1e1C6F82F651fA15E33AEfD07b", + "label": "WOO token (Arbitrum)" + }, + { + "id": "attacker-recipient", + "role": "attacker-eoa", + "address": "0xb59d04d9957C9e266dfF5c4173D4d2324Eb029Ad", + "label": "Attacker fund recipient address" + } + ], + "fundFlow": [ + { + "fromEntityId": "victim-pool", + "toEntityId": "attacker-contract", + "tokenSymbol": "USDC", + "amountHuman": "~$8,750,000 USDC/WETH equiv.", + "note": "Drained across three flash-loan attack iterations" + }, + { + "fromEntityId": "attacker-contract", + "toEntityId": "attacker-recipient", + "tokenSymbol": "ETH", + "amountHuman": "~559 WETH + USDC", + "note": "Profits swept to recipient after repaying flash loans" + } + ], + "sources": [ + { + "id": "rekt-woofi", + "kind": "forensic-thread", + "url": "https://rekt.news/woo-rekt/", + "publisher": "rekt.news" + }, + { + "id": "cyfrin-woofi", + "kind": "forensic-thread", + "url": "https://www.cyfrin.io/blog/hack-analysis-into-woofi-exploit", + "publisher": "Cyfrin" + }, + { + "id": "woo-postmortem", + "kind": "post-mortem", + "url": "https://woox.io/blog/en/woofi-spmm-exploit-post-mortem", + "publisher": "WOO / WOOFi Team" + }, + { + "id": "halborn-woofi", + "kind": "forensic-thread", + "url": "https://www.halborn.com/blog/post/explained-the-woofi-hack-march-2024", + "publisher": "Halborn" + } + ] +} diff --git a/src/utils/hack-analysis/llm.ts b/src/utils/hack-analysis/llm.ts new file mode 100644 index 0000000..01f6ddb --- /dev/null +++ b/src/utils/hack-analysis/llm.ts @@ -0,0 +1,105 @@ +// src/utils/hack-analysis/llm.ts +import { hackAnalysisSchema, type HackAnalysis, type Incident } from "./types"; +import type { EvidencePacket } from "../tx-analysis/types"; +import type { ClassifierLabel } from "./classifier"; +import type { LlmInvokeFn } from "../tx-analysis/llm"; + +export const HACK_SYSTEM_PROMPT = `You are TxCaptain-Forensics, writing a hack-incident post-mortem from on-chain evidence ONLY. + +Rules you MUST follow: +1. Ground every attackSteps[i].evidenceIds entry in the evidence ids provided (writes w_*, reads r_*, triggers t_*, profit p_*). If an id is not in the evidence, do NOT cite it. +2. Do NOT invent addresses, tokens, amounts, or txs not present in the evidence or the analogous incidents. +3. Treat the classifier labels as a floor. Add classes only when the evidence clearly supports them. Remove classes that the evidence contradicts. +4. If the evidence is insufficient, return verdict HACK_UNCERTAIN or LOOKS_BENIGN and list gaps in missingEvidence. +5. Use analogous incidents for framing ONLY. Do not copy their addresses or amounts. Cite which analog ids informed the framing in analogIncidentIds. +6. Respond with a JSON object matching the declared schema exactly. No prose outside JSON. + +Required JSON shape (all keys present; use null / [] / "" when unknown): +{ + "verdict": "HACK_CONFIRMED" | "HACK_LIKELY" | "HACK_UNCERTAIN" | "LOOKS_BENIGN", + "confidence": number 0..1, + "headline": string, + "coreContradiction": string, + "exploitClasses": [ { "class": string, "confidence": number, "rationale": string } ], + "entities": [ { "role": string, "address": "0x..." (EIP-55), "label": string } ], + "attackSteps": [ { "order": number, "label": string, "detail": string, "evidenceIds": string[] } ], + "fundFlow": [ { "fromLabel": string, "toLabel": string, "tokenSymbol": string|null, "amountHuman": string|null } ], + "analogIncidentIds": string[], + "missingEvidence": string[], + "caveats": string[] +}`; + +export interface RunHackAnalysisInput { + packet: EvidencePacket; + labels: ClassifierLabel[]; + analogs: Incident[]; + invoke: LlmInvokeFn; + signal?: AbortSignal; +} + +export function validateEvidenceReferences( + analysis: HackAnalysis, + packet: EvidencePacket, + analogs: Incident[], +): { badEvidenceIds: string[]; badAnalogIds: string[] } { + const validIds = new Set([ + ...packet.writes.map((w) => w.id), + ...packet.reads.map((r) => r.id), + ...packet.triggers.map((t) => t.id), + ...packet.profit.map((p) => p.id), + ]); + const validAnalogIds = new Set(analogs.map((a) => a.id)); + const badEvidenceIds = Array.from( + new Set(analysis.attackSteps.flatMap((s) => s.evidenceIds.filter((id) => !validIds.has(id)))), + ); + const badAnalogIds = analysis.analogIncidentIds.filter((id) => !validAnalogIds.has(id)); + return { badEvidenceIds, badAnalogIds }; +} + +function buildUserPrompt(input: RunHackAnalysisInput): string { + return JSON.stringify({ + instruction: "Analyze the following tx as a suspected hack and return the structured JSON.", + classifierLabels: input.labels, + analogousIncidents: input.analogs.map((a) => ({ + id: a.id, name: a.name, protocol: a.protocol, exploitClasses: a.exploitClasses, + coreContradiction: a.coreContradiction, tldr: a.tldr, + })), + evidence: { + txHash: input.packet.txHash, chainId: input.packet.chainId, + from: input.packet.from, to: input.packet.to, + success: input.packet.success, revertReason: input.packet.revertReason, + writes: input.packet.writes, reads: input.packet.reads, + triggers: input.packet.triggers, profit: input.packet.profit, + contracts: input.packet.contracts, heuristics: input.packet.heuristics, + truncated: input.packet.truncated, + }, + }); +} + +export async function runHackAnalysis(input: RunHackAnalysisInput): Promise { + const raw = await input.invoke({ + system: HACK_SYSTEM_PROMPT, + user: buildUserPrompt(input), + responseSchema: hackAnalysisSchema, + signal: input.signal, + }); + const parsed = hackAnalysisSchema.parse(raw); + const report = validateEvidenceReferences(parsed, input.packet, input.analogs); + + if (report.badEvidenceIds.length === 0 && report.badAnalogIds.length === 0) return parsed; + + const cleaned: HackAnalysis = { + ...parsed, + attackSteps: parsed.attackSteps.map((s) => ({ + ...s, + evidenceIds: s.evidenceIds.filter((id) => !report.badEvidenceIds.includes(id)), + })), + analogIncidentIds: parsed.analogIncidentIds.filter((id) => !report.badAnalogIds.includes(id)), + caveats: [ + ...parsed.caveats, + ...(report.badEvidenceIds.length ? [`LLM dropped ${report.badEvidenceIds.length} unknown evidenceIds: ${report.badEvidenceIds.join(", ")}`] : []), + ...(report.badAnalogIds.length ? [`LLM dropped ${report.badAnalogIds.length} unknown analogIncidentIds: ${report.badAnalogIds.join(", ")}`] : []), + ], + }; + return cleaned; +} diff --git a/src/utils/hack-analysis/retrieval.ts b/src/utils/hack-analysis/retrieval.ts new file mode 100644 index 0000000..09dbb84 --- /dev/null +++ b/src/utils/hack-analysis/retrieval.ts @@ -0,0 +1,36 @@ +import type { Incident } from "./types"; +import type { ClassifierLabel } from "./classifier"; + +export interface RetrievalInput { + labels: ClassifierLabel[]; + corpus: Incident[]; + chainHint: string; + k: number; +} + +export function retrieveAnalogs({ + labels, + corpus, + chainHint, + k, +}: RetrievalInput): Incident[] { + const wanted = new Set( + labels.filter((l) => l.class !== "unknown").map((l) => l.class) + ); + if (wanted.size === 0) return []; + + const scored = corpus + .map((incident) => { + const overlap = incident.exploitClasses.filter((c) => + wanted.has(c) + ).length; + if (overlap === 0) return null; + + const chainBonus = incident.chain === chainHint ? 0.25 : 0; + return { incident, score: overlap + chainBonus }; + }) + .filter((x): x is { incident: Incident; score: number } => x !== null); + + scored.sort((a, b) => b.score - a.score); + return scored.slice(0, k).map((x) => x.incident); +} diff --git a/src/utils/hack-analysis/triage/cofhe.ts b/src/utils/hack-analysis/triage/cofhe.ts new file mode 100644 index 0000000..0c5d708 --- /dev/null +++ b/src/utils/hack-analysis/triage/cofhe.ts @@ -0,0 +1,27 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; +import { extractTriageBits } from "./features"; + +export interface TriageResult { + classBits: number; + severity: number; +} + +const CLASS_LABELS: ReadonlyArray = [ + [1 << 0, "flashloan-price-manipulation"], + [1 << 1, "delegatecall-to-user-controlled"], + [1 << 2, "approval-drain"], + [1 << 3, "signer-compromise"], + [1 << 4, "access-control-bypass"], +]; + +export function classBitsToLabels(bits: number): string[] { + const out: string[] = []; + for (const [mask, label] of CLASS_LABELS) { + if (bits & mask) out.push(label); + } + return out; +} + +export function packFeatures(packet: EvidencePacket): number { + return extractTriageBits(packet); +} diff --git a/src/utils/hack-analysis/triage/features.ts b/src/utils/hack-analysis/triage/features.ts new file mode 100644 index 0000000..f5b4b96 --- /dev/null +++ b/src/utils/hack-analysis/triage/features.ts @@ -0,0 +1,123 @@ +import type { EvidencePacket } from "../../tx-analysis/types"; + +// MUST stay byte-for-byte aligned with HackTriage.sol constants. +export const FEATURE_BITS = { + hasFlashSel: 0, + hasTransferFromExt: 1, + hasDelegatecall: 2, + hasSlot0Write: 3, + targetUnverified: 4, + targetIsSafe: 5, + attackerProfitIn: 6, + attackerProfitLarge: 7, + repeatedWritesMax3: 8, + privilegedWriteLabel: 9, + hasOracleStaticcall: 10, + hasBridgeFn: 11, +} as const; + +const FLASH_SELECTORS = new Set([ + "0xab9c4b5d", // Aave V2 flashLoan + "0x42b0b77c", // Aave V3 flashLoanSimple + "0x5c38449e", // Balancer V2 flashLoan + "0x5cffe9de", // dYdX soloMargin operate + "0xfa461e33", // uniswapV3SwapCallback — flash-loan-equivalent entrypoint + "0x10d1e85c", // uniswapV2Call — pair callback, flash-swap entrypoint + "0x23e30c8b", // ERC-3156 onFlashLoan receiver +]); +const ORACLE_SELECTORS = new Set(["0x50d25bcd", "0xfeaf968c", "0xb3596f07"]); +const TRANSFER_FROM = "0x23b872dd"; +const TRANSFER_SEL = "0xa9059cbb"; +// Raw-units threshold for "large" transfer when packet.profit/heuristics are missing. +// 1e11 ≈ $100k at 6 decimals; negligible at 18 decimals so low risk of tripping benign txs. +const LARGE_TRANSFER_RAW = 10n ** 11n; +const TO_ARG_RE = /^(_?to|dst|recipient)$/i; +const AMOUNT_ARG_RE = /^(_?value|wad|amount|_amount)$/i; +// Must match the classifier.ts adjusted SAFE_NAMES (no bare "safe" to avoid false-positives on non-Gnosis contracts named "Safe"). +const SAFE_NAMES = new Set(["gnosissafe", "gnosissafeproxy", "safeproxy"]); +const PRIV_LABEL_RE = /owner|admin|role|minter|guardian|governor/i; +const BRIDGE_FN_RE = /message|vaa|execute|verify|process/i; + +export function extractTriageBits(packet: EvidencePacket): number { + let bits = 0; + const set = (p: keyof typeof FEATURE_BITS) => { bits |= 1 << FEATURE_BITS[p]; }; + + const from = packet.from.toLowerCase(); + const to = packet.to?.toLowerCase() ?? ""; + const tr = packet.triggers; + + if (tr.some((t) => t.selector && FLASH_SELECTORS.has(t.selector.toLowerCase()))) set("hasFlashSel"); + if (tr.some((t) => t.kind === "DELEGATECALL")) set("hasDelegatecall"); + if (tr.some((t) => t.kind === "STATICCALL" && t.selector && ORACLE_SELECTORS.has(t.selector.toLowerCase()))) set("hasOracleStaticcall"); + if (tr.some((t) => t.function && BRIDGE_FN_RE.test(t.function))) set("hasBridgeFn"); + if (tr.some((t) => { + if (t.kind !== "CALL" || t.selector?.toLowerCase() !== TRANSFER_FROM) return false; + const fromArg = t.args.find((a) => a.name === "from")?.value?.toString().toLowerCase(); + return !!fromArg && fromArg !== from; + })) set("hasTransferFromExt"); + + if (packet.writes.some((w) => w.slot === "0x0" || w.slot === "0x00")) set("hasSlot0Write"); + if (packet.writes.some((w) => w.label !== null && PRIV_LABEL_RE.test(w.label))) set("privilegedWriteLabel"); + + const slotCounts = new Map(); + for (const w of packet.writes) { + const k = `${w.contract.toLowerCase()}:${w.slot}`; + slotCounts.set(k, (slotCounts.get(k) ?? 0) + 1); + } + if ([...slotCounts.values()].some((c) => c >= 3)) set("repeatedWritesMax3"); + + const toMeta = packet.contracts.find((c) => c.address.toLowerCase() === to); + // targetUnverified mirrors the delegatecall rule: any delegatecall target is explicitly unverified, + // OR the tx target itself is unverified. This matches the "unverified callee" notion the classifier keys off. + const delegatecallUnverified = tr.some((t) => { + if (t.kind !== "DELEGATECALL") return false; + const meta = packet.contracts.find((c) => c.address.toLowerCase() === t.contract.toLowerCase()); + return meta !== undefined && meta.verified === false; + }); + if (toMeta?.verified === false || delegatecallUnverified) set("targetUnverified"); + if (toMeta?.name && SAFE_NAMES.has(toMeta.name.replace(/\s+/g, "").toLowerCase())) set("targetIsSafe"); + + if (packet.profit.some((p) => p.direction === "in" && p.holder.toLowerCase() === from)) set("attackerProfitIn"); + if (packet.heuristics.some((h) => h.name === "large_delta")) set("attackerProfitLarge"); + + // Fallback inferences when upstream tracer omits profit/writes/heuristics. + // A raw trace with only triggers (typical for real on-chain tx re-analysis) still has enough + // signal to classify the obvious cases. Guarded against non-fixture shapes by only firing when + // the transfer recipient is the tx origin and values live in a sane range. + const isTransferCall = (t: typeof tr[number]): boolean => { + if (t.kind !== "CALL") return false; + const sel = t.selector?.toLowerCase(); + return sel === TRANSFER_SEL || sel === TRANSFER_FROM; + }; + + if (!(bits & (1 << FEATURE_BITS.attackerProfitIn))) { + const profitInferred = tr.some((t) => { + if (!isTransferCall(t)) return false; + const toArg = t.args.find((a) => TO_ARG_RE.test(a.name))?.value?.toString().toLowerCase(); + return !!toArg && toArg === from; + }); + if (profitInferred) set("attackerProfitIn"); + } + + if (!(bits & (1 << FEATURE_BITS.attackerProfitLarge))) { + const largeInferred = tr.some((t) => { + if (!isTransferCall(t)) return false; + const valueArg = t.args.find((a) => AMOUNT_ARG_RE.test(a.name))?.value?.toString(); + if (!valueArg) return false; + try { return BigInt(valueArg) >= LARGE_TRANSFER_RAW; } catch { return false; } + }); + if (largeInferred) set("attackerProfitLarge"); + } + + if (!(bits & (1 << FEATURE_BITS.repeatedWritesMax3))) { + const transfersByToken = new Map(); + for (const t of tr) { + if (!isTransferCall(t)) continue; + const k = t.contract.toLowerCase(); + transfersByToken.set(k, (transfersByToken.get(k) ?? 0) + 1); + } + if ([...transfersByToken.values()].some((c) => c >= 3)) set("repeatedWritesMax3"); + } + + return bits & 0xFFFF; +} diff --git a/src/utils/hack-analysis/types.ts b/src/utils/hack-analysis/types.ts new file mode 100644 index 0000000..26d95c3 --- /dev/null +++ b/src/utils/hack-analysis/types.ts @@ -0,0 +1,152 @@ +import { z } from "zod"; +import { getAddress, isAddress } from "viem"; + +export const EXPLOIT_CLASSES = [ + "reentrancy", + "flashloan-price-manipulation", + "oracle-manipulation", + "approval-drain", + "delegatecall-to-user-controlled", + "signer-compromise", + "bridge-message-forgery", + "governance-takeover", + "access-control-bypass", + "math-invariant-manipulation", +] as const; + +export const CLASSIFIER_CLASSES = [...EXPLOIT_CLASSES, "unknown"] as const; + +export const exploitClassSchema = z.enum(EXPLOIT_CLASSES); +export const classifierClassSchema = z.enum(CLASSIFIER_CLASSES); +export type ExploitClass = z.infer; +export type ClassifierClass = z.infer; + +export const entityRoleSchema = z.enum([ + "attacker-eoa", "attacker-contract", "victim-contract", "victim-eoa", + "router", "oracle", "bridge", "signer", + "implementation", "proxy", "token", "pool", +]); +export type EntityRole = z.infer; + +const EVM_CHAINS = ["ethereum", "bsc", "polygon", "arbitrum", "optimism", "avalanche", "base", "fantom"] as const; + +const checksumAddressSchema = z + .string() + .refine( + (a) => isAddress(a) && getAddress(a) === a, + { message: "address must be EIP-55 checksummed" }, + ); + +const evmTxHashSchema = z.string().regex(/^0x[0-9a-fA-F]{64}$/u); + +const isoDate2022PlusSchema = z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/u) + .refine((d) => d >= "2022-01-01", { message: "date must be 2022 or later" }); + +export const incidentSchema = z.object({ + id: z.string().min(1), + name: z.string().min(1), + chain: z.enum(EVM_CHAINS), + date: isoDate2022PlusSchema, + protocol: z.string().nullable(), + amountUsd: z.number().nonnegative().nullable(), + canonicalTxs: z.array(evmTxHashSchema), + exploitClasses: z.array(exploitClassSchema).min(1), + tldr: z.string().min(1), + coreContradiction: z.string().min(1), + attackSteps: z.array( + z.object({ + order: z.number().int().nonnegative(), + label: z.string().min(1), + detail: z.string().min(1), + sourceIds: z.array(z.string()), + }), + ), + entities: z.array( + z.object({ + id: z.string().min(1), + role: entityRoleSchema, + address: checksumAddressSchema, + label: z.string().min(1), + }), + ), + fundFlow: z.array( + z.object({ + fromEntityId: z.string().min(1), + toEntityId: z.string().min(1), + tokenSymbol: z.string().nullable(), + amountHuman: z.string().nullable(), + note: z.string().nullable(), + }), + ), + sources: z.array( + z.object({ + id: z.string().min(1), + kind: z.enum(["post-mortem", "official", "forensic-thread", "database-row"]), + url: z.string().url(), + publisher: z.string().min(1), + }), + ), +}); +export type Incident = z.infer; + +export function validateCrossRefs(incident: Incident): string[] { + const errors: string[] = []; + const entityIds = new Set(incident.entities.map((e) => e.id)); + const sourceIds = new Set(incident.sources.map((s) => s.id)); + + for (const f of incident.fundFlow) { + if (!entityIds.has(f.fromEntityId)) errors.push(`fundFlow.fromEntityId '${f.fromEntityId}' not in entities[]`); + if (!entityIds.has(f.toEntityId)) errors.push(`fundFlow.toEntityId '${f.toEntityId}' not in entities[]`); + } + for (const s of incident.attackSteps) { + for (const srcId of s.sourceIds) { + if (!sourceIds.has(srcId)) errors.push(`attackSteps[${s.order}].sourceId '${srcId}' not in sources[]`); + } + } + return errors; +} + +export const hackAnalysisSchema = z.object({ + verdict: z.enum(["HACK_CONFIRMED", "HACK_LIKELY", "HACK_UNCERTAIN", "LOOKS_BENIGN"]), + confidence: z.number().min(0).max(1), + headline: z.string().min(1), + coreContradiction: z.string(), + exploitClasses: z.array( + z.object({ + class: exploitClassSchema, + confidence: z.number().min(0).max(1), + rationale: z.string(), + }), + ), + entities: z.array( + z.object({ + role: entityRoleSchema, + address: checksumAddressSchema, + label: z.string(), + }), + ), + attackSteps: z.array( + z.object({ + order: z.number().int().nonnegative(), + label: z.string(), + detail: z.string(), + evidenceIds: z.array(z.string()), + }), + ), + fundFlow: z.array( + z.object({ + fromLabel: z.string(), + toLabel: z.string(), + tokenSymbol: z.string().nullable(), + amountHuman: z.string().nullable(), + }), + ), + analogIncidentIds: z.array(z.string()), + missingEvidence: z.array(z.string()), + caveats: z.array(z.string()), +}); +export type HackAnalysis = z.infer; + +export const toChecksum = (lower: string): string => getAddress(lower as `0x${string}`); diff --git a/src/utils/heimdall/heimdallApi.ts b/src/utils/heimdall/heimdallApi.ts new file mode 100644 index 0000000..83ee842 --- /dev/null +++ b/src/utils/heimdall/heimdallApi.ts @@ -0,0 +1,93 @@ +import { z } from "zod"; +import { getBridgeHeaders, getSimulatorBridgeUrl } from "@/utils/env"; +import { + heimdallDecompilationSchema, + heimdallErrorSchema, + heimdallStorageDumpSchema, + heimdallVersionSchema, + type HeimdallDecompilation, + type HeimdallStorageDump, + type HeimdallVersion, +} from "./types"; + +export class HeimdallApiError extends Error { + readonly code: string; + readonly status: number; + readonly details?: string; + constructor(code: string, status: number, message: string, details?: string) { + super(message); + this.name = "HeimdallApiError"; + this.code = code; + this.status = status; + this.details = details; + } +} + +function bridgeBase(): string { + const base = getSimulatorBridgeUrl(); + if (!base) { + throw new HeimdallApiError("bridge_disabled", 503, "Simulator bridge is disabled"); + } + return base; +} + +async function post(path: string, body: unknown, schema: z.ZodType): Promise { + const res = await fetch(`${bridgeBase()}${path}`, { + method: "POST", + headers: getBridgeHeaders(), + body: JSON.stringify(body), + }); + + const text = await res.text(); + let parsed: unknown; + try { + parsed = text ? JSON.parse(text) : {}; + } catch { + throw new HeimdallApiError("heimdall_invalid_output", res.status, "Non-JSON response", text.slice(0, 500)); + } + + if (!res.ok) { + const err = heimdallErrorSchema.safeParse(parsed); + if (err.success) { + throw new HeimdallApiError(err.data.error, res.status, err.data.message ?? err.data.error, err.data.details); + } + throw new HeimdallApiError("heimdall_upstream_error", res.status, `HTTP ${res.status}`); + } + + const ok = schema.safeParse(parsed); + if (!ok.success) { + throw new HeimdallApiError( + "heimdall_invalid_output", + res.status, + `schema error: ${ok.error.issues[0]?.message ?? "unknown"}`, + ); + } + return ok.data; +} + +export function fetchHeimdallVersion(): Promise { + return post("/heimdall/version", {}, heimdallVersionSchema); +} + +export interface DecompilationRequest { + bytecode?: string; + address?: string; + chainId?: number; +} +export function fetchHeimdallDecompilation( + req: DecompilationRequest, +): Promise { + return post("/heimdall/decompile", req, heimdallDecompilationSchema); +} + +export interface StorageDumpRequest { + address: string; + chainId: number; + blockNumber?: number; + blockTag?: string; +} +export function fetchHeimdallStorageDump( + req: StorageDumpRequest, +): Promise { + return post("/heimdall/dump", req, heimdallStorageDumpSchema); +} diff --git a/src/utils/heimdall/hooks.ts b/src/utils/heimdall/hooks.ts new file mode 100644 index 0000000..37c7aa6 --- /dev/null +++ b/src/utils/heimdall/hooks.ts @@ -0,0 +1,63 @@ +import { useQuery, type UseQueryOptions } from "@tanstack/react-query"; +import { + fetchHeimdallDecompilation, + fetchHeimdallStorageDump, + fetchHeimdallVersion, + type DecompilationRequest, + type StorageDumpRequest, +} from "./heimdallApi"; +import type { + HeimdallDecompilation, + HeimdallStorageDump, + HeimdallVersion, +} from "./types"; + +const HEIMDALL_STALE_MS = 10 * 60 * 1000; + +export function useHeimdallVersion( + options?: Partial>, +) { + return useQuery({ + queryKey: ["heimdall", "version"], + queryFn: () => fetchHeimdallVersion(), + staleTime: HEIMDALL_STALE_MS, + ...options, + }); +} + +export function useHeimdallDecompilation( + req: DecompilationRequest, + options?: Partial>, +) { + const key = req.bytecode + ? ["heimdall", "decompile", "bytecode", req.bytecode.toLowerCase()] + : req.address && req.chainId + ? ["heimdall", "decompile", "address", req.address.toLowerCase(), req.chainId] + : ["heimdall", "decompile", "empty"]; + + return useQuery({ + queryKey: key, + queryFn: () => fetchHeimdallDecompilation(req), + enabled: Boolean(req.bytecode || (req.address && req.chainId)), + staleTime: HEIMDALL_STALE_MS, + ...options, + }); +} + +export function useHeimdallStorageDump( + req: StorageDumpRequest, + options?: Partial>, +) { + return useQuery({ + queryKey: [ + "heimdall", + "dump", + req.address.toLowerCase(), + req.chainId, + req.blockNumber ?? req.blockTag ?? "latest", + ], + queryFn: () => fetchHeimdallStorageDump(req), + staleTime: HEIMDALL_STALE_MS, + ...options, + }); +} diff --git a/src/utils/heimdall/types.ts b/src/utils/heimdall/types.ts new file mode 100644 index 0000000..5e0fc5b --- /dev/null +++ b/src/utils/heimdall/types.ts @@ -0,0 +1,80 @@ +import { z } from "zod"; + +const hex32 = z + .string() + .regex(/^0x[0-9a-fA-F]{64}$/, "expected 0x-prefixed 32-byte hex"); +const hexVar = z.string().regex(/^0x[0-9a-fA-F]+$/, "expected 0x-prefixed hex"); +const address = z + .string() + .regex(/^0x[0-9a-fA-F]{40}$/, "expected 20-byte address"); + +type AbiParam = { + name?: string; + type: string; + indexed?: boolean; + components?: AbiParam[]; +}; +const abiParam: z.ZodType = z.object({ + name: z.string().optional(), + type: z.string(), + indexed: z.boolean().optional(), + components: z.array(z.lazy(() => abiParam)).optional(), +}); +const abiEntry = z.object({ + type: z.enum(["function", "event", "error", "constructor", "fallback", "receive"]), + name: z.string().optional(), + inputs: z.array(abiParam).optional(), + outputs: z.array(abiParam).optional(), + stateMutability: z.string().optional(), + anonymous: z.boolean().optional(), +}); + +export const heimdallDecompilationSchema = z.object({ + source: z.string().min(1), + abi: z.array(abiEntry), + bytecodeHash: hex32, + heimdallVersion: z.string(), + cacheHit: z.boolean(), + generatedAt: z.number(), +}); + +export const heimdallStorageSlotSchema = z.object({ + slot: hexVar, + value: hex32, + modifiers: z.array(z.string()).optional(), +}); + +export const heimdallStorageDumpSchema = z.object({ + address, + chainId: z.number().int().positive(), + blockNumber: z.number().int().nonnegative(), + slots: z.array(heimdallStorageSlotSchema), + heimdallVersion: z.string(), + cacheHit: z.boolean(), + generatedAt: z.number(), +}); + +export const heimdallErrorSchema = z.object({ + error: z.enum([ + "heimdall_not_installed", + "heimdall_timeout", + "heimdall_crash", + "heimdall_invalid_output", + "heimdall_upstream_error", + "heimdall_rpc_failed", + "bad_request", + ]), + message: z.string().optional(), + details: z.string().optional(), +}); + +export const heimdallVersionSchema = z.object({ + available: z.boolean(), + version: z.string().optional(), +}); + +export type HeimdallDecompilation = z.infer; +export type HeimdallStorageSlot = z.infer; +export type HeimdallStorageDump = z.infer; +export type HeimdallError = z.infer; +export type HeimdallVersion = z.infer; diff --git a/src/utils/heimdall/useHeimdallAvailability.ts b/src/utils/heimdall/useHeimdallAvailability.ts new file mode 100644 index 0000000..57d4ae9 --- /dev/null +++ b/src/utils/heimdall/useHeimdallAvailability.ts @@ -0,0 +1,19 @@ +import { useHeimdallVersion } from "./hooks"; + +export interface HeimdallAvailability { + isAvailable: boolean; + isLoading: boolean; + version?: string; +} + +export function useHeimdallAvailability(): HeimdallAvailability { + const { data, isLoading } = useHeimdallVersion({ + throwOnError: false, + retry: false, + }); + return { + isAvailable: Boolean(data?.available), + isLoading, + version: data?.version, + }; +} diff --git a/src/utils/heuristic-layout/abiLabelExtractor.ts b/src/utils/heuristic-layout/abiLabelExtractor.ts new file mode 100644 index 0000000..6ffef88 --- /dev/null +++ b/src/utils/heuristic-layout/abiLabelExtractor.ts @@ -0,0 +1,53 @@ +import type { HeimdallDecompilation } from "@/utils/heimdall/types"; + +export interface AbiHint { + label: string; + typeHint: string; + preferredSlot: number | null; +} + +type AbiEntry = HeimdallDecompilation["abi"][number]; + +interface Recipe { + inputs: string[]; + outputs: string[]; + typeHint: string; +} + +const ERC20_LIKE: Record = { + owner: { inputs: [], outputs: ["address"], typeHint: "t_address" }, + totalSupply: { inputs: [], outputs: ["uint256"], typeHint: "t_uint256" }, + name: { inputs: [], outputs: ["string"], typeHint: "t_string_storage" }, + symbol: { inputs: [], outputs: ["string"], typeHint: "t_string_storage" }, + decimals: { inputs: [], outputs: ["uint8"], typeHint: "t_uint8" }, + paused: { inputs: [], outputs: ["bool"], typeHint: "t_bool" }, + balanceOf: { inputs: ["address"], outputs: ["uint256"], typeHint: "t_mapping_address_uint256" }, + allowance: { inputs: ["address", "address"], outputs: ["uint256"], typeHint: "t_mapping_address_mapping_address_uint256" }, +}; + +function matchesRecipe(entry: AbiEntry, recipe: Recipe): boolean { + if (entry.type !== "function") return false; + if (entry.stateMutability !== "view" && entry.stateMutability !== "pure") return false; + const inputs = (entry.inputs ?? []).map((i) => i.type); + const outputs = (entry.outputs ?? []).map((o) => o.type); + if (inputs.length !== recipe.inputs.length) return false; + if (outputs.length !== recipe.outputs.length) return false; + for (let i = 0; i < inputs.length; i++) if (inputs[i] !== recipe.inputs[i]) return false; + for (let i = 0; i < outputs.length; i++) if (outputs[i] !== recipe.outputs[i]) return false; + return true; +} + +export function extractAbiHints(abi: AbiEntry[]): AbiHint[] { + const seen = new Set(); + const out: AbiHint[] = []; + for (const entry of abi) { + if (entry.type !== "function" || !entry.name) continue; + const recipe = ERC20_LIKE[entry.name]; + if (!recipe) continue; + if (!matchesRecipe(entry, recipe)) continue; + if (seen.has(entry.name)) continue; + seen.add(entry.name); + out.push({ label: entry.name, typeHint: recipe.typeHint, preferredSlot: null }); + } + return out; +} diff --git a/src/utils/heuristic-layout/heuristicLayout.ts b/src/utils/heuristic-layout/heuristicLayout.ts new file mode 100644 index 0000000..6ee382d --- /dev/null +++ b/src/utils/heuristic-layout/heuristicLayout.ts @@ -0,0 +1,109 @@ +import type { + StorageLayoutEntry, + StorageLayoutResponse, + StorageTypeDefinition, +} from "@/types/debug"; +import type { + HeimdallDecompilation, + HeimdallStorageDump, +} from "@/utils/heimdall/types"; +import { extractAbiHints } from "./abiLabelExtractor"; + +const SYNTHETIC_MAPPING_SLOT_BASE = 1_000_000; + +const BASE_TYPES: Record = { + t_bytes32: { encoding: "inplace", label: "bytes32", numberOfBytes: "32" }, + t_uint256: { encoding: "inplace", label: "uint256", numberOfBytes: "32" }, + t_address: { encoding: "inplace", label: "address", numberOfBytes: "20" }, + t_bool: { encoding: "inplace", label: "bool", numberOfBytes: "1" }, + t_uint8: { encoding: "inplace", label: "uint8", numberOfBytes: "1" }, + t_string_storage: { + encoding: "bytes", + label: "string", + numberOfBytes: "32", + }, + t_mapping_address_uint256: { + encoding: "mapping", + label: "mapping(address => uint256)", + numberOfBytes: "32", + key: "t_address", + value: "t_uint256", + }, + t_mapping_address_mapping_address_uint256: { + encoding: "mapping", + label: "mapping(address => mapping(address => uint256))", + numberOfBytes: "32", + key: "t_address", + value: "t_mapping_address_uint256", + }, +}; + +function slotToNumericString(hexSlot: string): string { + try { + return BigInt(hexSlot).toString(10); + } catch { + return "0"; + } +} + +export interface SynthesizeParams { + dump: HeimdallStorageDump; + decompilation?: HeimdallDecompilation; +} + +export function synthesizeHeuristicLayout( + { dump, decompilation }: SynthesizeParams, +): StorageLayoutResponse { + const storage: StorageLayoutEntry[] = []; + const types: Record = {}; + const usedLabels = new Set(); + + const ensureType = (key: string) => { + if (!types[key] && BASE_TYPES[key]) types[key] = BASE_TYPES[key]; + }; + + const sorted = [...dump.slots].sort((a, b) => { + const an = BigInt(a.slot); + const bn = BigInt(b.slot); + return an < bn ? -1 : an > bn ? 1 : 0; + }); + + for (const slotEntry of sorted) { + const modifier = (slotEntry.modifiers && slotEntry.modifiers.length === 1) + ? slotEntry.modifiers[0] + : null; + const label = modifier ?? `slot_${slotEntry.slot}`; + const typeKey = modifier ? "t_uint256" : "t_bytes32"; + ensureType(typeKey); + usedLabels.add(label); + storage.push({ + astId: storage.length, + contract: "HeuristicDecompilation", + label, + offset: 0, + slot: slotToNumericString(slotEntry.slot), + type: typeKey, + }); + } + + if (decompilation?.abi?.length) { + const hints = extractAbiHints(decompilation.abi); + let syntheticIndex = 0; + for (const hint of hints) { + if (usedLabels.has(hint.label)) continue; + usedLabels.add(hint.label); + ensureType(hint.typeHint); + storage.push({ + astId: storage.length, + contract: "HeuristicDecompilation", + label: hint.label, + offset: 0, + slot: String(SYNTHETIC_MAPPING_SLOT_BASE + syntheticIndex), + type: hint.typeHint, + }); + syntheticIndex += 1; + } + } + + return { storage, types }; +} diff --git a/src/utils/llm/streamParser.ts b/src/utils/llm/streamParser.ts new file mode 100644 index 0000000..3347771 --- /dev/null +++ b/src/utils/llm/streamParser.ts @@ -0,0 +1,60 @@ +export type StreamEvent = + | { type: "text"; value: string } + | { type: "done" } + | { type: "error"; message: string }; + +function linesOf(chunk: string): string[] { + return chunk.split(/\r?\n/); +} + +function dataPayloads(chunk: string): string[] { + return linesOf(chunk) + .filter((l) => l.startsWith("data: ")) + .map((l) => l.slice(6).trim()) + .filter(Boolean); +} + +export function parseAnthropicSseChunk(chunk: string): StreamEvent[] { + const events: StreamEvent[] = []; + for (const payload of dataPayloads(chunk)) { + try { + const msg = JSON.parse(payload); + if (msg.type === "content_block_delta" && msg.delta?.type === "text_delta") { + events.push({ type: "text", value: msg.delta.text }); + } else if (msg.type === "message_stop") { + events.push({ type: "done" }); + } + } catch { /* ignore */ } + } + return events; +} + +export function parseOpenAISseChunk(chunk: string): StreamEvent[] { + const events: StreamEvent[] = []; + for (const payload of dataPayloads(chunk)) { + if (payload === "[DONE]") { + events.push({ type: "done" }); + continue; + } + try { + const msg = JSON.parse(payload); + const delta = msg?.choices?.[0]?.delta?.content; + if (typeof delta === "string") events.push({ type: "text", value: delta }); + } catch { /* ignore */ } + } + return events; +} + +export function parseGeminiSseChunk(chunk: string): StreamEvent[] { + const events: StreamEvent[] = []; + for (const payload of dataPayloads(chunk)) { + try { + const msg = JSON.parse(payload); + const parts = msg?.candidates?.[0]?.content?.parts ?? []; + for (const p of parts) { + if (typeof p?.text === "string") events.push({ type: "text", value: p.text }); + } + } catch { /* ignore */ } + } + return events; +} diff --git a/src/utils/llm/types.ts b/src/utils/llm/types.ts new file mode 100644 index 0000000..3f3c6be --- /dev/null +++ b/src/utils/llm/types.ts @@ -0,0 +1,64 @@ +export type LlmProvider = "anthropic" | "openai" | "gemini" | "custom"; + +export type LlmMode = "live" | "fixture" | "off"; + +export interface LlmProviderConfig { + provider: LlmProvider; + model: string; + apiKey?: string; // BYOK; undefined for hexkit-proxied default (gemini) + customBaseUrl?: string; // only when provider === "custom"; enforces browser-direct path +} + +export interface LlmMessage { + role: "system" | "user" | "assistant"; + content: string; +} + +export interface LlmRequest { + task: string; // telemetry tag e.g. "vault-intent" or "tx-analysis-simple" + provider: LlmProvider; + model: string; + messages: LlmMessage[]; + stream?: boolean; + responseFormat?: "json" | "text"; + schema?: TSchema; // zod schema for JSON validation (client-side) + maxRetries?: number; // default 2 + signal?: AbortSignal; +} + +export interface LlmResponse { + text: string; + parsed?: TParsed; // populated when schema supplied and validation succeeds + provider: LlmProvider; + model: string; + usage?: { inputTokens?: number; outputTokens?: number }; +} + +export type LlmErrorClass = + | "bad_key" + | "rate_limit" + | "network" + | "context_overflow" + | "schema_invalid" + | "provider_down" + | "unauthorized_endpoint" + | "consent_required" + | "unknown"; + +export class LlmError extends Error { + errorClass: LlmErrorClass; + provider: LlmProvider; + retryable: boolean; + constructor( + errorClass: LlmErrorClass, + message: string, + provider: LlmProvider, + retryable: boolean, + ) { + super(message); + this.name = "LlmError"; + this.errorClass = errorClass; + this.provider = provider; + this.retryable = retryable; + } +} diff --git a/src/utils/resolver/ContractResolver.ts b/src/utils/resolver/ContractResolver.ts index 5b56bf3..d162a45 100644 --- a/src/utils/resolver/ContractResolver.ts +++ b/src/utils/resolver/ContractResolver.ts @@ -303,14 +303,6 @@ class ContractResolver { case 'blockscout': return fetchBlockscout(address, chain, options.blockscoutApiKey, signal); - case 'blockscout-ebd': - // TODO: Implement Blockscout EBD (bytecode database) source - return { success: false, error: 'Blockscout EBD not yet implemented' }; - - case 'whatsabi': - // TODO: Implement WhatsABI source for unverified contracts - return { success: false, error: 'WhatsABI not yet implemented' }; - default: return { success: false, error: `Unknown source: ${source}` }; } diff --git a/src/utils/resolver/contractContext.ts b/src/utils/resolver/contractContext.ts index a273e35..207bdbe 100644 --- a/src/utils/resolver/contractContext.ts +++ b/src/utils/resolver/contractContext.ts @@ -41,6 +41,7 @@ import { contractResolver } from './ContractResolver'; import { hasDiamondLoupeFunctions } from './diamondLoupe'; import { detectTokenType, type TokenDetectionResult } from '../universalTokenDetector'; import { networkConfigManager } from '../../config/networkConfig'; +import { ZERO_ADDRESS } from '../addressConstants'; export interface ContractContext { address: string; @@ -238,7 +239,6 @@ function getAbiItemKey(item: AbiItem): string | null { } const IMPLEMENTATION_SELECTOR = '0x5c60da1b'; // implementation() -const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; function slotToAddress(slotValue: string): string | null { if (!slotValue || slotValue === '0x' || slotValue === '0x0') return null; diff --git a/src/utils/resolver/proxyResolver.ts b/src/utils/resolver/proxyResolver.ts index 744892e..32bcee1 100644 --- a/src/utils/resolver/proxyResolver.ts +++ b/src/utils/resolver/proxyResolver.ts @@ -18,6 +18,7 @@ import { ethers } from 'ethers'; import type { Chain } from '../../types'; import type { ProxyInfo, ProxyType } from './types'; import { getSharedProvider } from '../providerPool'; +import { ZERO_ADDRESS } from '../addressConstants'; let diamondResolverPromise: Promise | null = null; const loadDiamondResolver = () => { @@ -61,9 +62,6 @@ const EIP1167_REGEX = new RegExp( 'i' ); -// Zero address constant -const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; - // Cache TTL (5 minutes) const CACHE_TTL_MS = 5 * 60 * 1000; diff --git a/src/utils/resolver/types.ts b/src/utils/resolver/types.ts index b96046a..ed407f6 100644 --- a/src/utils/resolver/types.ts +++ b/src/utils/resolver/types.ts @@ -7,7 +7,7 @@ import type { Chain } from '../../types'; -export type Source = 'sourcify' | 'etherscan' | 'blockscout' | 'blockscout-ebd' | 'whatsabi'; +export type Source = 'sourcify' | 'etherscan' | 'blockscout'; export type Confidence = 'verified' | 'inferred' | 'bytecode-only'; @@ -226,8 +226,6 @@ export const SOURCE_CONFIGS: Record = { sourcify: { name: 'sourcify', timeout: 4000, priority: 1 }, etherscan: { name: 'etherscan', timeout: 5000, priority: 2, rateLimit: 5 }, blockscout: { name: 'blockscout', timeout: 5000, priority: 3 }, - 'blockscout-ebd': { name: 'blockscout-ebd', timeout: 8000, priority: 4 }, - whatsabi: { name: 'whatsabi', timeout: 6000, priority: 5 }, }; export const isVerifiedConfidence = (confidence: Confidence): boolean => diff --git a/src/utils/simulationArtifactTypes.ts b/src/utils/simulationArtifactTypes.ts index 2ed6fb7..dfce0ee 100644 --- a/src/utils/simulationArtifactTypes.ts +++ b/src/utils/simulationArtifactTypes.ts @@ -1,9 +1,17 @@ /** - * Type definitions for simulation artifacts. - * - * Extracted from simulationArtifacts.ts to keep each module under 800 lines. + * Type definitions and shared leaf helpers for simulation artifacts. */ +export const ensureArray = (value: unknown, mapFn?: (item: any) => T): T[] => { + if (Array.isArray(value)) { + return mapFn ? value.map(mapFn) : (value as T[]); + } + if (value && typeof value === "object") { + return mapFn ? [mapFn(value)] : [value as T]; + } + return []; +}; + export type SimulationCallNode = { frameKey: string; type?: string; diff --git a/src/utils/simulationArtifacts.ts b/src/utils/simulationArtifacts.ts index a567027..6227cfe 100644 --- a/src/utils/simulationArtifacts.ts +++ b/src/utils/simulationArtifacts.ts @@ -41,15 +41,8 @@ import { convertEdbTraceToArtifacts, normalizeAssetChangeEntry, buildOpcodeTrace // ---- shared utilities ------------------------------------------------- -export const ensureArray = (value: unknown, mapFn?: (item: any) => T): T[] => { - if (Array.isArray(value)) { - return mapFn ? value.map(mapFn) : (value as T[]); - } - if (value && typeof value === "object") { - return mapFn ? [mapFn(value)] : [value as T]; - } - return []; -}; +export { ensureArray } from "./simulationArtifactTypes"; +import { ensureArray } from "./simulationArtifactTypes"; export const normalizeHex = (value: unknown): string | null => { if (typeof value !== "string") return null; diff --git a/src/utils/solidity-layout/allocatorTypeHelpers.ts b/src/utils/solidity-layout/allocatorTypeHelpers.ts index 25d6a83..c396cb6 100644 --- a/src/utils/solidity-layout/allocatorTypeHelpers.ts +++ b/src/utils/solidity-layout/allocatorTypeHelpers.ts @@ -1,9 +1,6 @@ /** * Type resolution, sizing, encoding, and label-building helpers - * for the Solidity slot allocator. - * - * Extracted from allocator.ts to keep each module under 800 lines. - */ + * for the Solidity slot allocator. */ import type { ParsedTypeName, diff --git a/src/utils/solidity-layout/types.ts b/src/utils/solidity-layout/types.ts index c0b1c40..8396f7d 100644 --- a/src/utils/solidity-layout/types.ts +++ b/src/utils/solidity-layout/types.ts @@ -73,7 +73,7 @@ export interface ParsedEnumDef { export interface ReconstructionResult { layout: StorageLayoutResponse; /** 'compiler' is never returned by this module -- included for upstream compat */ - confidence: 'compiler' | 'reconstructed'; + confidence: 'compiler' | 'reconstructed' | 'heuristic'; warnings: string[]; } diff --git a/src/utils/traceDecoder/analysisHelpers.ts b/src/utils/traceDecoder/analysisHelpers.ts index 6c80a00..df68aba 100644 --- a/src/utils/traceDecoder/analysisHelpers.ts +++ b/src/utils/traceDecoder/analysisHelpers.ts @@ -1,9 +1,6 @@ /** * Phase 2 helpers: multi-contract map building, call frame processing, - * and PC-resolution closure factories. - * - * Extracted from decodeTraceAnalysis.ts to keep files under 800 lines. - */ + * and PC-resolution closure factories. */ import { ethers } from "ethers"; import type { DecodedTraceRow, PcInfo, DecodeTraceContext, FunctionRange } from './types'; diff --git a/src/utils/traceDecoder/callHierarchy.ts b/src/utils/traceDecoder/callHierarchy.ts index 377419e..d489c7f 100644 --- a/src/utils/traceDecoder/callHierarchy.ts +++ b/src/utils/traceDecoder/callHierarchy.ts @@ -1,9 +1,6 @@ /** * Phase 2c: Final row assembly, LOG decoding, internal call hierarchy - * building (FnCallInfo tracking, call stack walking), and frame anchoring. - * - * Extracted from decodeTraceAnalysis.ts to keep files under 800 lines. - */ + * building (FnCallInfo tracking, call stack walking), and frame anchoring. */ import type { DecodedTraceRow, DecodeTraceContext, FnCallInfo } from './types'; import { parseLogStack, decodeLogWithFallback } from './eventDecoding'; diff --git a/src/utils/traceDecoder/jumpAnalysis.ts b/src/utils/traceDecoder/jumpAnalysis.ts index f67ac82..2207551 100644 --- a/src/utils/traceDecoder/jumpAnalysis.ts +++ b/src/utils/traceDecoder/jumpAnalysis.ts @@ -1,9 +1,6 @@ /** * Phase 2b: Jump detection, reachability filtering, source-line rescue, - * deduplication, and return value decoding. - * - * Extracted from decodeTraceAnalysis.ts to keep files under 800 lines. - */ + * deduplication, and return value decoding. */ import type { DecodedTraceRow, DecodeTraceContext, FunctionRange } from './types'; import { formatDisplayVal } from './formatting'; diff --git a/src/utils/tx-analysis/deepDive.ts b/src/utils/tx-analysis/deepDive.ts new file mode 100644 index 0000000..23c9a53 --- /dev/null +++ b/src/utils/tx-analysis/deepDive.ts @@ -0,0 +1,62 @@ +import { sanitizeSolidity } from "./sourceSanitizer"; +import type { EvidencePacket } from "./types"; + +export type SourceQuality = "verified" | "reconstructed" | "heuristic" | "none"; + +export interface VerifiedSourceBundle { + contractName: string; + files: Array<{ path: string; source: string }>; + provider: "sourcify" | "etherscan" | "blockscout"; +} + +export interface HeimdallSourceBundle { + source: string; + provider: "heimdall"; +} + +export interface DeepDiveDependencies { + packet: EvidencePacket; + fetchVerifiedSource: (address: string) => Promise; + fetchHeimdallDecompile?: (address: string) => Promise; +} + +export interface DeepDiveContext { + sources: Record; + qualityByContract: Record; +} + +export async function runDeepDive(deps: DeepDiveDependencies): Promise { + const pathContracts = new Set(); + for (const w of deps.packet.writes) pathContracts.add(w.contract); + for (const t of deps.packet.triggers) pathContracts.add(t.contract); + + const sources: Record = {}; + const quality: Record = {}; + + for (const addr of pathContracts) { + const verified = await deps.fetchVerifiedSource(addr).catch(() => null); + if (verified && verified.files.length > 0) { + const merged = verified.files + .map((f) => sanitizeSolidity(f.source, { filePath: f.path })) + .filter((r) => !r.dropped) + .map((r) => r.sanitized) + .join("\n\n// ---- file boundary ----\n\n"); + if (merged.trim().length > 0) { + sources[addr] = merged; + quality[addr] = "verified"; + continue; + } + } + if (deps.fetchHeimdallDecompile) { + const heimdall = await deps.fetchHeimdallDecompile(addr).catch(() => null); + if (heimdall) { + sources[addr] = heimdall.source; + quality[addr] = "heuristic"; + continue; + } + } + quality[addr] = "none"; + } + + return { sources, qualityByContract: quality }; +} diff --git a/src/utils/tx-analysis/extractor.ts b/src/utils/tx-analysis/extractor.ts new file mode 100644 index 0000000..3e8752d --- /dev/null +++ b/src/utils/tx-analysis/extractor.ts @@ -0,0 +1,237 @@ +import { EVIDENCE_ROW_CAPS, evidencePacketSchema } from "./types"; +import type { + EvidencePacket, + WriteEvidence, + ReadEvidence, + TriggerEvidence, + ProfitEvidence, + ContractMeta, +} from "./types"; +import type { BridgeSimulationResponsePayload } from "../transaction-simulation/types"; + +const TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; + +interface V3Row { + id?: number; + kind?: string; + name?: string; + pc?: number; + contract?: string | null; + sourceFile?: string | null; + line?: number | null; + storage_write?: { slot?: string; before?: string | null; after?: string } | null; + storage_read?: { slot?: string; value?: string } | null; + entryMeta?: { + callType?: string; + target?: string; + codeAddress?: string; + selector?: string; + function?: string; + args?: Array<{ name: string; value: string }>; + } | null; + logInfo?: { topics?: string[] } | null; + decodedLog?: { args?: Array<{ name: string | number; value: string }> } | null; +} + +export interface ExtractorInput { + simulationId: string; + from: string; + to: string | null; + simulation: BridgeSimulationResponsePayload; + txHash: string | null; + contracts?: ContractMeta[]; +} + +const normalizeHex = (value: string | null | undefined): string | null => { + if (!value) return null; + return value.toLowerCase().startsWith("0x") + ? value.toLowerCase() + : `0x${value.toLowerCase()}`; +}; + +const normalizeAddress = (value: string | null | undefined): string | null => { + const hex = normalizeHex(value); + if (!hex || hex.length !== 42) return null; + return hex; +}; + +const topicToAddress = (topic: string | undefined | null): string | null => { + if (!topic) return null; + const hex = topic.toLowerCase().replace(/^0x/, ""); + if (hex.length < 40) return null; + return `0x${hex.slice(-40)}`; +}; + +const pickTransferAmount = (row: V3Row): string => { + const arg = row.decodedLog?.args?.find((a) => /value|amount|wad|tokens/i.test(String(a.name))); + if (arg?.value) return String(arg.value); + return "0"; +}; + +function findPrecedingWrite( + writes: WriteEvidence[], + contract: string, + slot: string, +): string | null { + for (let i = writes.length - 1; i >= 0; i -= 1) { + const w = writes[i]; + if (w.contract === contract && w.slot === slot) return w.id; + } + return null; +} + +export function extractEvidence(input: ExtractorInput): EvidencePacket { + const trace = input.simulation.renderedTrace; + const writes: WriteEvidence[] = []; + const reads: ReadEvidence[] = []; + const triggers: TriggerEvidence[] = []; + const profit: ProfitEvidence[] = []; + + const rows: V3Row[] = Array.isArray(trace?.rows) ? (trace!.rows as V3Row[]) : []; + const truncated = { writes: false, reads: false, triggers: false, profit: false }; + + const fromAddr = (normalizeAddress(input.from) ?? input.from).toLowerCase(); + + const pushProfit = (p: ProfitEvidence) => { + if (profit.length >= EVIDENCE_ROW_CAPS.profit) { + truncated.profit = true; + return; + } + profit.push(p); + }; + + for (const row of rows) { + const contract = normalizeAddress(row.contract); + const opcodeIndex = typeof row.id === "number" && row.id >= 0 ? row.id : 0; + + if (row.storage_write && contract) { + if (writes.length >= EVIDENCE_ROW_CAPS.writes) { + truncated.writes = true; + } else { + const slot = normalizeHex(row.storage_write.slot) ?? "0x0"; + const valueAfter = normalizeHex(row.storage_write.after) ?? "0x0"; + writes.push({ + id: `w_${writes.length}`, + contract, + slot, + valueBefore: normalizeHex(row.storage_write.before), + valueAfter, + label: null, + typeHint: null, + opcodeIndex, + sourceLine: row.line ?? null, + sourceFile: row.sourceFile ?? null, + }); + } + } + + if (row.storage_read && contract) { + if (reads.length >= EVIDENCE_ROW_CAPS.reads) { + truncated.reads = true; + } else { + const slot = normalizeHex(row.storage_read.slot) ?? "0x0"; + reads.push({ + id: `r_${reads.length}`, + contract, + slot, + value: normalizeHex(row.storage_read.value) ?? "0x0", + label: null, + opcodeIndex, + sourceLine: row.line ?? null, + sourceFile: row.sourceFile ?? null, + followsWriteId: findPrecedingWrite(writes, contract, slot), + }); + } + } + + if (row.entryMeta && row.entryMeta.callType) { + if (triggers.length >= EVIDENCE_ROW_CAPS.triggers) { + truncated.triggers = true; + } else { + const target = + normalizeAddress(row.entryMeta.codeAddress ?? row.entryMeta.target) ?? contract; + if (target) { + triggers.push({ + id: `t_${triggers.length}`, + contract: target, + kind: row.entryMeta.callType as TriggerEvidence["kind"], + selector: row.entryMeta.selector ?? null, + function: row.entryMeta.function ?? null, + args: row.entryMeta.args ?? [], + logTopics: [], + opcodeIndex, + }); + } + } + } + + if ( + (row.name === "LOG1" || row.name === "LOG2" || row.name === "LOG3" || row.name === "LOG4") && + contract + ) { + const topics = row.logInfo?.topics?.map((t) => String(t)) ?? []; + if (triggers.length >= EVIDENCE_ROW_CAPS.triggers) { + truncated.triggers = true; + } else { + triggers.push({ + id: `t_${triggers.length}`, + contract, + kind: "LOG", + selector: null, + function: null, + args: + row.decodedLog?.args?.map((a) => ({ name: String(a.name), value: a.value })) ?? [], + logTopics: topics, + opcodeIndex, + }); + } + const topic0 = (topics[0] ?? "").toLowerCase(); + if (topic0 === TRANSFER_TOPIC0 && topics.length >= 3) { + const transferFrom = topicToAddress(topics[1]); + const transferTo = topicToAddress(topics[2]); + const delta = pickTransferAmount(row); + if (transferFrom && transferFrom.toLowerCase() === fromAddr) { + pushProfit({ + id: `p_${profit.length}`, + token: contract, + asset: "ERC20", + holder: transferFrom, + delta, + direction: "out", + opcodeIndex, + }); + } + if (transferTo && transferTo.toLowerCase() === fromAddr) { + pushProfit({ + id: `p_${profit.length}`, + token: contract, + asset: "ERC20", + holder: transferTo, + delta, + direction: "in", + opcodeIndex, + }); + } + } + } + } + + const packet: EvidencePacket = { + txHash: input.txHash, + simulationId: input.simulationId, + chainId: input.simulation.chainId ?? 1, + from: (normalizeAddress(input.from) ?? input.from) as string, + to: input.to ? ((normalizeAddress(input.to) ?? input.to) as string) : null, + success: Boolean(input.simulation.success), + revertReason: input.simulation.revertReason ?? null, + writes, + reads, + triggers, + profit, + contracts: input.contracts ?? [], + heuristics: [], + truncated, + }; + + return evidencePacketSchema.parse(packet); +} diff --git a/src/utils/tx-analysis/llm.ts b/src/utils/tx-analysis/llm.ts new file mode 100644 index 0000000..ad0981f --- /dev/null +++ b/src/utils/tx-analysis/llm.ts @@ -0,0 +1,119 @@ +import type { z } from "zod"; +import { verdictSchema, type EvidencePacket, type Verdict } from "./types"; + +const SYSTEM_PROMPT = `You are TxCaptain, a forensic EVM transaction analyst. You produce structured JSON verdicts grounded in the evidence provided. Follow TxAnalyzer methodology: +- Prefer SSTORE evidence over call-path reasoning. +- Build a Write → Read → Trigger → Profit causal chain. +- State a single Core Contradiction: what the contract expected vs. what actually happened. +- Never speculate beyond evidence. If you cannot confirm, return verdict=OPEN or INSUFFICIENT and list missingEvidence. +- Respond with a JSON object matching the declared schema exactly. Do not wrap in prose. + +VERDICT SELECTION RULES (apply strictly): +- "CONFIRMED" — Use when the evidence shows extracted value AND victim/protocol harm from at least one suspicious mechanism: (a) flash-loan executed by an unverified contract plus a victim/protocol loss or abnormal state contradiction, (b) a named access-control or accounting bypass, (c) sandwich/MEV pattern that drained a victim, (d) reentrancy with state inconsistency. The Profit step should be non-empty. Confidence 0.8+. +- "OPEN" — Unusual patterns where exploitation is plausible but not provable from this evidence alone: large unexplained transfers, unverified contracts touching value, MEV-shaped trades without clear victim, governance edge cases. Use this when a human analyst should look closer. +- "INSUFFICIENT" — Ordinary user activity ONLY: direct user calls to known-verified routers/protocols (1inch, Uniswap router, Aave, Compound, OpenSea, etc.) with patterns matching their public APIs (swap, cancelOrder, approve, bridge, mint, claim, vote, NFT trade). The caller is an EOA invoking a single named function on a verified contract. No flash loans, no contract-to-contract value extraction, no unverified intermediaries. Confidence 0.85+. + +Decision shortcuts: +- EOA calls verified router function (cancelOrder, swap, approve) → INSUFFICIENT. +- Public liquidation, atomic arbitrage, or ordinary backrun with profit but no victim drain / invariant break → OPEN unless the public protocol rules prove it is routine, then INSUFFICIENT. +- Unverified contract takes flash loan and ends with profit transferred out from a victim/protocol state contradiction → CONFIRMED (even without naming the exact bug — the pattern itself is the evidence). +- Anything in between (large transfers, unusual gates, unfamiliar contracts) → OPEN. + +Heuristic interpretations (apply when heuristicsFired includes these): +- "oracle_read_cluster" — Multiple oracle-shaped reads (latestAnswer / getReserves / getPrice / consult / ethToToken / priceOf) on the same contract within one tx. If the reads are adjacent to a flash loan or DEX swap on the same underlying market, call out oracle manipulation explicitly in coreContradiction and name the oracle contract. +- "admin_state_mutation" — Admin-role mutator (setOwner / upgradeTo / putCurEpochConKeepers / setKeepers / setPriceOracle / grantRole) executed during a tx that was not initiated by a governance/multisig address. Strong signal of access-control bypass (Poly Network, Ronin, Wormhole class). +- "suspicious_governance_call" — Cross-chain execute entry points (verifyHeaderAndExecuteTx, crossChainExecute). Combined with admin_state_mutation on the same tx, this indicates cross-chain message forgery. + +False negatives on real exploits are much worse than false positives on suspicious-but-benign txs — when in doubt between OPEN and CONFIRMED on a clearly profit-extracting unverified contract, lean CONFIRMED. When in doubt between INSUFFICIENT and OPEN on a routine user call, lean INSUFFICIENT. + +Required JSON shape (all keys must be present; use null or [] when unknown): +{ + "verdict": "CONFIRMED" | "OPEN" | "INSUFFICIENT", + "confidence": number between 0 and 1, + "coreContradiction": { "expected": string, "actual": string } | null, + "causalChain": [ { "step": "Write" | "Read" | "Trigger" | "Profit", "description": string, "evidenceId": string } ], + "gates": [ { "name": string, "bypassedBy": string | null } ], + "deepDive": null, + "riskBound": { "upperBoundEth": string, "rationale": string } | null, + "missingEvidence": [ string ] +}`; + +export type LlmInvokeFn = (opts: { + system: string; + user: string; + responseSchema: z.ZodType; + signal?: AbortSignal; +}) => Promise; + +export interface AnalysisInput { + packet: EvidencePacket; + signal?: AbortSignal; + invoke: LlmInvokeFn; +} + +export interface AnalysisResult extends Verdict { + promptHash: string; +} + +async function hashPrompt(prompt: string): Promise { + const data = new TextEncoder().encode(prompt); + const digest = await crypto.subtle.digest("SHA-256", data); + return Array.from(new Uint8Array(digest)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +function buildUserPrompt(packet: EvidencePacket): string { + return JSON.stringify({ + instruction: "Analyze the following EVM transaction evidence and return a verdict JSON.", + transaction: { + txHash: packet.txHash, + chainId: packet.chainId, + from: packet.from, + to: packet.to, + success: packet.success, + revertReason: packet.revertReason, + }, + heuristicsFired: packet.heuristics, + writes: packet.writes, + reads: packet.reads, + triggers: packet.triggers, + profit: packet.profit, + contracts: packet.contracts, + truncated: packet.truncated, + }); +} + +export async function runSimpleAnalysis(input: AnalysisInput): Promise { + const user = buildUserPrompt(input.packet); + const promptHash = await hashPrompt(`${SYSTEM_PROMPT}\n\n${user}`); + const raw = await input.invoke({ + system: SYSTEM_PROMPT, + user, + responseSchema: verdictSchema, + signal: input.signal, + }); + const parsed = verdictSchema.parse(raw); + return { ...parsed, promptHash }; +} + +export async function runComplexAnalysis( + input: AnalysisInput & { deepDiveContext: Record }, +): Promise { + const user = JSON.stringify({ + ...JSON.parse(buildUserPrompt(input.packet)), + deepDive: { + instruction: "Review the source excerpts below for trust-boundary issues and produce a deepDive section in your verdict.", + sources: input.deepDiveContext, + }, + }); + const promptHash = await hashPrompt(`${SYSTEM_PROMPT}\n\n${user}`); + const raw = await input.invoke({ + system: SYSTEM_PROMPT, + user, + responseSchema: verdictSchema, + signal: input.signal, + }); + const parsed = verdictSchema.parse(raw); + return { ...parsed, promptHash }; +} diff --git a/src/utils/tx-analysis/markdown.ts b/src/utils/tx-analysis/markdown.ts new file mode 100644 index 0000000..cd34c6c --- /dev/null +++ b/src/utils/tx-analysis/markdown.ts @@ -0,0 +1,52 @@ +import type { Verdict } from "./types"; + +export function verdictToMarkdown(v: Verdict): string { + const lines: string[] = []; + lines.push(`# Verdict: ${v.verdict}`, ""); + lines.push(`Confidence: ${(v.confidence * 100).toFixed(0)}%`, ""); + + if (v.coreContradiction) { + lines.push("## Core Contradiction", ""); + lines.push(`- Expected: ${v.coreContradiction.expected}`); + lines.push(`- Actual: ${v.coreContradiction.actual}`, ""); + } + + if (v.causalChain.length > 0) { + lines.push("## Causal Chain", ""); + for (const s of v.causalChain) { + lines.push(`1. **${s.step}** — ${s.description} (\`${s.evidenceId}\`)`); + } + lines.push(""); + } + + if (v.gates.length > 0) { + lines.push("## Gates", ""); + for (const g of v.gates) { + lines.push(`- \`${g.name}\`${g.bypassedBy ? ` — bypassed by ${g.bypassedBy}` : ""}`); + } + lines.push(""); + } + + if (v.riskBound) { + lines.push("## Risk Upper Bound", ""); + lines.push(`**${v.riskBound.upperBoundEth} ETH** — ${v.riskBound.rationale}`, ""); + } + + if (v.deepDive) { + lines.push("## Deep Dive", ""); + lines.push(`Verdict upgrade: ${v.deepDive.verdictUpgrade}`, ""); + for (const tb of v.deepDive.trustBoundaries) { + lines.push(`### ${tb.contract} (${tb.sourceQuality})`); + for (const f of tb.findings) lines.push(`- ${f}`); + lines.push(""); + } + } + + if (v.missingEvidence.length > 0) { + lines.push("## Missing Evidence", ""); + for (const m of v.missingEvidence) lines.push(`- ${m}`); + lines.push(""); + } + + return lines.join("\n").trimEnd(); +} diff --git a/src/utils/tx-analysis/normalizeVerdict.ts b/src/utils/tx-analysis/normalizeVerdict.ts new file mode 100644 index 0000000..373a0cc --- /dev/null +++ b/src/utils/tx-analysis/normalizeVerdict.ts @@ -0,0 +1,199 @@ +const VERDICT_SYNONYMS: Record = { + CONFIRMED: "CONFIRMED", + CONFIRMED_EXPLOIT: "CONFIRMED", + HACK_CONFIRMED: "CONFIRMED", + EXPLOIT: "CONFIRMED", + EXPLOIT_CONFIRMED: "CONFIRMED", + HACK: "CONFIRMED", + MALICIOUS: "CONFIRMED", + ATTACK: "CONFIRMED", + OPEN: "OPEN", + LIKELY_EXPLOIT: "OPEN", + PROBABLE_EXPLOIT: "OPEN", + POSSIBLE_EXPLOIT: "OPEN", + HACK_LIKELY: "OPEN", + LIKELY_MALICIOUS: "OPEN", + POSSIBLY_MALICIOUS: "OPEN", + SUSPICIOUS: "OPEN", + UNCERTAIN: "OPEN", + NEEDS_REVIEW: "OPEN", + AMBIGUOUS: "OPEN", + INSUFFICIENT: "INSUFFICIENT", + INSUFFICIENT_EVIDENCE: "INSUFFICIENT", + BENIGN: "INSUFFICIENT", + LIKELY_BENIGN: "INSUFFICIENT", + SAFE: "INSUFFICIENT", + ROUTINE: "INSUFFICIENT", + CLEAN: "INSUFFICIENT", + NONE: "INSUFFICIENT", +}; + +function normalizeVerdictLabel(label: string): "CONFIRMED" | "OPEN" | "INSUFFICIENT" | null { + const key = label.trim().toUpperCase().replace(/[\s-]+/g, "_"); + if (VERDICT_SYNONYMS[key]) return VERDICT_SYNONYMS[key]; + if (/^(LIKELY|PROBABLE|POSSIBLE)_/.test(key) && /(EXPLOIT|HACK|MALICIOUS|ATTACK)/.test(key)) { + return "OPEN"; + } + if (key.includes("CONFIRMED") && /(EXPLOIT|HACK|MALICIOUS|ATTACK)/.test(key)) { + return "CONFIRMED"; + } + if (/(BENIGN|SAFE|CLEAN|ROUTINE)/.test(key)) return "INSUFFICIENT"; + return null; +} + +const STEP_SYNONYMS: Record = { + WRITE: "Write", + SSTORE: "Write", + STORE: "Write", + READ: "Read", + SLOAD: "Read", + TRIGGER: "Trigger", + CALL: "Trigger", + EVENT: "Trigger", + LOG: "Trigger", + PROFIT: "Profit", + TRANSFER: "Profit", +}; + +/** + * Strips a markdown ```json ... ``` envelope or trims to the outermost JSON + * object so that JSON.parse can succeed even when the model wraps its reply + * in fences or surrounding prose. + */ +function stripJsonEnvelope(text: string): string { + const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/i); + if (fenced) return fenced[1].trim(); + const firstBrace = text.indexOf("{"); + const lastBrace = text.lastIndexOf("}"); + if (firstBrace >= 0 && lastBrace > firstBrace) { + return text.slice(firstBrace, lastBrace + 1); + } + return text.trim(); +} + +/** + * Accepts whatever useLlmInvocation surfaces (already-parsed object, or raw + * text with optional fences/prose) and returns a normalized JS object ready + * for verdictSchema.parse. Throws if no JSON object can be recovered. + */ +export function parseAndNormalizeVerdict(input: unknown): unknown { + if (input == null) { + throw new Error("LLM returned no payload to normalize"); + } + if (typeof input === "string") { + const stripped = stripJsonEnvelope(input); + let parsed: unknown; + try { + parsed = JSON.parse(stripped); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + throw new Error(`LLM did not return valid JSON: ${msg}`); + } + return normalizeVerdictPayload(parsed); + } + return normalizeVerdictPayload(input); +} + +/** + * Coerces a raw LLM payload toward the verdictSchema shape so that small + * deviations (verdict label synonyms, confidence as a percent, step name + * casing, missing optional arrays) don't trigger schema_invalid. + * + * Pure: returns a new object, never throws. Caller still passes the result + * through verdictSchema.parse for the final shape check. + */ +export function normalizeVerdictPayload(raw: unknown): unknown { + if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw; + const obj: Record = { ...(raw as Record) }; + + if (typeof obj.verdict === "string") { + const normalized = normalizeVerdictLabel(obj.verdict); + if (normalized) obj.verdict = normalized; + } + + if (typeof obj.confidence === "number" && Number.isFinite(obj.confidence)) { + let c = obj.confidence; + if (c > 1) c = c / 100; + if (c < 0) c = 0; + if (c > 1) c = 1; + obj.confidence = c; + } else if (typeof obj.confidence === "string") { + const parsed = Number.parseFloat(obj.confidence); + if (Number.isFinite(parsed)) { + let c = parsed; + if (c > 1) c = c / 100; + if (c < 0) c = 0; + if (c > 1) c = 1; + obj.confidence = c; + } + } + + if (Array.isArray(obj.causalChain)) { + obj.causalChain = obj.causalChain + .map((entry) => { + if (!entry || typeof entry !== "object") return null; + const e = entry as Record; + let step = e.step; + if (typeof step === "string") { + const key = step.trim().toUpperCase(); + if (STEP_SYNONYMS[key]) step = STEP_SYNONYMS[key]; + } + if (step !== "Write" && step !== "Read" && step !== "Trigger" && step !== "Profit") return null; + const description = typeof e.description === "string" ? e.description : null; + const evidenceId = + typeof e.evidenceId === "string" + ? e.evidenceId + : typeof e.evidence_id === "string" + ? e.evidence_id + : null; + if (description == null || evidenceId == null) return null; + return { step, description, evidenceId }; + }) + .filter((s): s is { step: "Write" | "Read" | "Trigger" | "Profit"; description: string; evidenceId: string } => s !== null); + } else if (obj.causalChain == null) { + obj.causalChain = []; + } + + if (Array.isArray(obj.gates)) { + obj.gates = obj.gates + .map((g) => { + if (!g || typeof g !== "object") return null; + const gate = g as Record; + if (typeof gate.name !== "string") return null; + const bypassedBy = + typeof gate.bypassedBy === "string" + ? gate.bypassedBy + : typeof gate.bypassed_by === "string" + ? gate.bypassed_by + : null; + return { name: gate.name, bypassedBy }; + }) + .filter((g): g is { name: string; bypassedBy: string | null } => g !== null); + } else if (obj.gates == null) { + obj.gates = []; + } + + if (Array.isArray(obj.missingEvidence)) { + obj.missingEvidence = obj.missingEvidence.filter((s): s is string => typeof s === "string"); + } else if (obj.missingEvidence == null) { + obj.missingEvidence = []; + } + + if (obj.coreContradiction && typeof obj.coreContradiction === "object" && !Array.isArray(obj.coreContradiction)) { + const cc = obj.coreContradiction as Record; + if (typeof cc.expected !== "string" || typeof cc.actual !== "string") { + obj.coreContradiction = null; + } + } else if (obj.coreContradiction !== null && obj.coreContradiction !== undefined) { + obj.coreContradiction = null; + } + + if (obj.riskBound && typeof obj.riskBound === "object" && !Array.isArray(obj.riskBound)) { + const rb = obj.riskBound as Record; + if (typeof rb.upperBoundEth !== "string" || typeof rb.rationale !== "string") { + obj.riskBound = null; + } + } + + return obj; +} diff --git a/src/utils/tx-analysis/sieve.ts b/src/utils/tx-analysis/sieve.ts new file mode 100644 index 0000000..feba723 --- /dev/null +++ b/src/utils/tx-analysis/sieve.ts @@ -0,0 +1,178 @@ +import type { EvidencePacket, HeuristicHit, TriggerEvidence } from "./types"; + +const ZERO_ADDR = "0x0000000000000000000000000000000000000000"; +const LARGE_DELTA_THRESHOLD = BigInt("0x8ac7230489e80000"); + +const ORACLE_FN_PATTERNS = [ + /^latestAnswer\b/i, + /^latestRoundData\b/i, + /^getPrice\b/i, + /priceOf\b/i, + /^getUnderlyingPrice\b/i, + /^getReserves\b/i, + /^consult\b/i, + /^quote\b/i, + /^getAmountsOut\b/i, + /^getAmountsIn\b/i, + /^ethToToken(Input|Output)?/i, + /^tokenToEth(Input|Output)?/i, + /^price0CumulativeLast\b/i, + /^price1CumulativeLast\b/i, + /\bFetchPrice\b/i, + /\bgetSpot\b/i, +]; + +const ADMIN_FN_PATTERNS = [ + /^setOwner\b/i, + /^transferOwnership\b/i, + /^upgradeTo\b/i, + /^upgradeToAndCall\b/i, + /^setAdmin\b/i, + /^grantRole\b/i, + /^revokeRole\b/i, + /^setKeeper(s)?\b/i, + /^addKeeper(s)?\b/i, + /^putCurEpochConKeepers\b/i, + /^changeBookKeeper\b/i, + /^_setPendingImplementation\b/i, + /^_acceptImplementation\b/i, + /^setPriceOracle\b/i, + /^setOracle\b/i, + /^setComptroller\b/i, +]; + +const GOVERNANCE_FN_PATTERNS = [ + /^execute(Transaction|Proposal)?\b/i, + /^crossChainExecute\b/i, + /^verifyHeaderAndExecuteTx\b/i, +]; + +const matchesAny = (name: string, patterns: RegExp[]): boolean => + patterns.some((p) => p.test(name)); + +function classifyTrigger(t: TriggerEvidence): Array { + const hits: Array = []; + const fn = t.function ?? ""; + if (!fn) return hits; + if ((t.kind === "STATICCALL" || t.kind === "CALL") && matchesAny(fn, ORACLE_FN_PATTERNS)) { + hits.push("oracle_read_cluster"); + } + if ((t.kind === "CALL" || t.kind === "DELEGATECALL") && matchesAny(fn, ADMIN_FN_PATTERNS)) { + hits.push("admin_state_mutation"); + } + if (matchesAny(fn, GOVERNANCE_FN_PATTERNS)) { + hits.push("suspicious_governance_call"); + } + return hits; +} + +export function applyHeuristics(packet: EvidencePacket): EvidencePacket { + const hits: HeuristicHit[] = []; + + if (!packet.success && packet.revertReason) { + hits.push({ + name: "revert_on_path", + evidenceId: "_tx", + reason: `Transaction reverted: ${packet.revertReason}`, + }); + } + + for (const r of packet.reads) { + if (r.followsWriteId) { + hits.push({ + name: "sload_after_sstore", + evidenceId: r.id, + reason: `SLOAD ${r.slot} follows SSTORE ${r.followsWriteId} on ${r.contract}`, + }); + } + } + + for (const t of packet.triggers) { + for (const a of t.args) { + if (typeof a.value === "string" && a.value.toLowerCase() === ZERO_ADDR) { + hits.push({ + name: "zero_address_transfer", + evidenceId: t.id, + reason: `Argument ${a.name} is the zero address`, + }); + break; + } + } + } + + for (const p of packet.profit) { + try { + const abs = p.delta.startsWith("-") ? BigInt(p.delta.slice(1)) : BigInt(p.delta); + if (abs >= LARGE_DELTA_THRESHOLD) { + hits.push({ + name: "large_delta", + evidenceId: p.id, + reason: `Balance delta ${p.delta} exceeds 10-ETH threshold`, + }); + } + } catch { + // non-numeric delta (e.g. NFT id) + } + } + + const oracleReadCounts = new Map(); + const adminWriteHits: Array<{ id: string; fn: string; contract: string }> = []; + const governanceHits: Array<{ id: string; fn: string; contract: string }> = []; + + for (const t of packet.triggers) { + const classifications = classifyTrigger(t); + for (const kind of classifications) { + if (kind === "oracle_read_cluster") { + const list = oracleReadCounts.get(t.contract) ?? []; + list.push(t.id); + oracleReadCounts.set(t.contract, list); + } else if (kind === "admin_state_mutation") { + adminWriteHits.push({ id: t.id, fn: t.function ?? "", contract: t.contract }); + } else if (kind === "suspicious_governance_call") { + governanceHits.push({ id: t.id, fn: t.function ?? "", contract: t.contract }); + } + } + } + + for (const [contract, ids] of oracleReadCounts) { + if (ids.length >= 2) { + hits.push({ + name: "oracle_read_cluster", + evidenceId: ids[0], + reason: `${ids.length} oracle-shaped reads on ${contract} (ids: ${ids.slice(0, 4).join(", ")}) — possible oracle manipulation`, + }); + } + } + for (const h of adminWriteHits) { + hits.push({ + name: "admin_state_mutation", + evidenceId: h.id, + reason: `Admin-role function ${h.fn} invoked on ${h.contract}`, + }); + } + for (const h of governanceHits) { + hits.push({ + name: "suspicious_governance_call", + evidenceId: h.id, + reason: `Cross-chain / governance execute function ${h.fn} invoked on ${h.contract}`, + }); + } + + const writeCountsBySlot = new Map(); + for (const w of packet.writes) { + const key = `${w.contract}:${w.slot}`; + writeCountsBySlot.set(key, (writeCountsBySlot.get(key) ?? 0) + 1); + } + for (const [key, count] of writeCountsBySlot) { + if (count >= 3) { + const evidenceId = packet.writes.find((w) => `${w.contract}:${w.slot}` === key)?.id ?? "_unknown"; + hits.push({ + name: "accumulator", + evidenceId, + reason: `Slot ${key} written ${count} times within one tx`, + }); + } + } + + return { ...packet, heuristics: hits }; +} diff --git a/src/utils/tx-analysis/sourceSanitizer.ts b/src/utils/tx-analysis/sourceSanitizer.ts new file mode 100644 index 0000000..a206745 --- /dev/null +++ b/src/utils/tx-analysis/sourceSanitizer.ts @@ -0,0 +1,53 @@ +export interface SanitizeOptions { + filePath: string; + maxChars?: number; +} + +export interface SanitizeResult { + sanitized: string; + removedLines: number; + dropped: boolean; + truncated: boolean; +} + +const VENDORED_PATTERNS = [ + /^@openzeppelin\//, + /^@chainlink\//, + /\/openzeppelin\//, + /\/node_modules\//, +]; + +export function sanitizeSolidity(source: string, opts: SanitizeOptions): SanitizeResult { + for (const p of VENDORED_PATTERNS) { + if (p.test(opts.filePath)) { + return { sanitized: "", removedLines: 0, dropped: true, truncated: false }; + } + } + + const lines = source.split(/\r?\n/u); + const kept: string[] = []; + let removed = 0; + for (const line of lines) { + const trimmed = line.trim(); + if ( + trimmed.startsWith("// SPDX") || + trimmed.startsWith("pragma solidity") || + trimmed.startsWith("pragma abicoder") || + trimmed.startsWith("import ") + ) { + removed += 1; + continue; + } + kept.push(line); + } + + let out = kept.join("\n"); + const max = opts.maxChars ?? 8000; + let truncated = false; + if (out.length > max) { + out = `${out.slice(0, max)}\n// ... [truncated by tx-analysis sanitizer] ...`; + truncated = true; + } + + return { sanitized: out, removedLines: removed, dropped: false, truncated }; +} diff --git a/src/utils/tx-analysis/types.ts b/src/utils/tx-analysis/types.ts new file mode 100644 index 0000000..4bf5ff1 --- /dev/null +++ b/src/utils/tx-analysis/types.ts @@ -0,0 +1,167 @@ +import { z } from "zod"; + +export const addressSchema = z + .string() + .regex(/^0x[0-9a-fA-F]{40}$/u, "invalid address"); + +export const bytes32Schema = z + .string() + .regex(/^0x[0-9a-fA-F]{1,64}$/u, "invalid hex value"); + +export const evidenceKindSchema = z.enum(["write", "read", "trigger", "profit"]); + +export const writeEvidenceSchema = z.object({ + id: z.string(), + contract: addressSchema, + slot: bytes32Schema, + valueBefore: bytes32Schema.nullable(), + valueAfter: bytes32Schema, + label: z.string().nullable(), + typeHint: z.string().nullable(), + opcodeIndex: z.number().int().nonnegative(), + sourceLine: z.number().int().nullable(), + sourceFile: z.string().nullable(), +}); + +export const readEvidenceSchema = z.object({ + id: z.string(), + contract: addressSchema, + slot: bytes32Schema, + value: bytes32Schema, + label: z.string().nullable(), + opcodeIndex: z.number().int().nonnegative(), + sourceLine: z.number().int().nullable(), + sourceFile: z.string().nullable(), + followsWriteId: z.string().nullable(), +}); + +export const triggerEvidenceSchema = z.object({ + id: z.string(), + contract: addressSchema, + kind: z.enum(["CALL", "DELEGATECALL", "STATICCALL", "CREATE", "CREATE2", "LOG"]), + selector: z.string().nullable(), + function: z.string().nullable(), + args: z.array(z.object({ name: z.string(), value: z.string() })).default([]), + logTopics: z.array(z.string()).default([]), + opcodeIndex: z.number().int().nonnegative(), +}); + +export const profitEvidenceSchema = z.object({ + id: z.string(), + token: addressSchema.nullable(), + asset: z.enum(["ETH", "ERC20", "ERC721", "ERC1155"]), + holder: addressSchema, + delta: z.string(), + direction: z.enum(["in", "out"]), + opcodeIndex: z.number().int().nonnegative(), +}); + +export const heuristicHitSchema = z.object({ + name: z.enum([ + "revert_on_path", + "sload_after_sstore", + "large_delta", + "accumulator", + "zero_address_transfer", + "balance_zeroed", + "self_destruct", + "oracle_read_cluster", + "admin_state_mutation", + "suspicious_governance_call", + ]), + evidenceId: z.string(), + reason: z.string(), +}); + +export const contractMetaSchema = z.object({ + address: addressSchema, + name: z.string().nullable(), + proxyImplementation: addressSchema.nullable(), + verified: z.boolean(), + sourceProvider: z.enum(["sourcify", "etherscan", "blockscout", "heimdall", "none"]), +}); + +export const evidencePacketSchema = z.object({ + txHash: z.string().nullable(), + simulationId: z.string(), + chainId: z.number().int(), + from: addressSchema, + // `to` is null for contract-creation txs. + to: addressSchema.nullable(), + success: z.boolean(), + revertReason: z.string().nullable(), + writes: z.array(writeEvidenceSchema), + reads: z.array(readEvidenceSchema), + triggers: z.array(triggerEvidenceSchema), + profit: z.array(profitEvidenceSchema), + contracts: z.array(contractMetaSchema), + heuristics: z.array(heuristicHitSchema), + truncated: z.object({ + writes: z.boolean(), + reads: z.boolean(), + triggers: z.boolean(), + profit: z.boolean(), + }), +}); + +export const causalStepSchema = z.object({ + step: z.enum(["Write", "Read", "Trigger", "Profit"]), + description: z.string(), + evidenceId: z.string(), +}); + +export const gateSchema = z.object({ + name: z.string(), + bypassedBy: z.string().nullable(), +}); + +export const riskBoundSchema = z.object({ + upperBoundEth: z.string(), + rationale: z.string(), +}); + +export const deepDiveSchema = z.object({ + trustBoundaries: z.array( + z.object({ + contract: addressSchema, + sourceQuality: z.enum(["verified", "reconstructed", "heuristic", "none"]), + findings: z.array(z.string()), + }), + ), + verdictUpgrade: z.enum(["CONFIRMED", "OPEN", "INSUFFICIENT"]), + additionalRiskBound: riskBoundSchema.nullable(), +}); + +export const verdictSchema = z.object({ + verdict: z.enum(["CONFIRMED", "OPEN", "INSUFFICIENT"]), + confidence: z.number().min(0).max(1), + coreContradiction: z + .object({ expected: z.string(), actual: z.string() }) + .nullish() + .transform((v) => v ?? null), + causalChain: z.array(causalStepSchema).default([]), + gates: z.array(gateSchema).default([]), + deepDive: deepDiveSchema.nullish().transform((v) => v ?? null), + riskBound: riskBoundSchema.nullish().transform((v) => v ?? null), + missingEvidence: z.array(z.string()).default([]), +}); + +export type EvidencePacket = z.infer; +export type WriteEvidence = z.infer; +export type ReadEvidence = z.infer; +export type TriggerEvidence = z.infer; +export type ProfitEvidence = z.infer; +export type HeuristicHit = z.infer; +export type ContractMeta = z.infer; +export type Verdict = z.infer; +export type CausalStep = z.infer; +export type Gate = z.infer; +export type RiskBound = z.infer; +export type DeepDive = z.infer; + +export const EVIDENCE_ROW_CAPS = { + writes: 200, + reads: 200, + triggers: 150, + profit: 100, +} as const; diff --git a/vercel.json b/vercel.json index 0ed268a..533b97e 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,13 @@ { "rewrites": [ + { + "source": "/api/edb", + "destination": "/api/edb-proxy" + }, + { + "source": "/api/edb/:path*", + "destination": "/api/edb-proxy?path=:path*" + }, { "source": "/api/sourcify/repository/:path*", "destination": "https://repo.sourcify.dev/:path*" diff --git a/vite.config.ts b/vite.config.ts index 7d95ffe..b53a8b7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -135,6 +135,95 @@ function llmProxyPlugin(envObj: Record): Plugin { }; } +function llmInvokeProxyPlugin(env: Record): Plugin { + const PROCESS_ENV_KEYS = [ + "GEMINI_API_KEY", + "GEMINI_BASE_URL", + "ANTHROPIC_API_KEY", + "ANTHROPIC_BASE_URL", + "OPENAI_API_KEY", + "OPENAI_BASE_URL", + "PROXY_SECRET", + "ALLOWED_ORIGINS", + "LLM_GUARD_CONFIG", + ]; + for (const k of PROCESS_ENV_KEYS) { + if (env[k] !== undefined && process.env[k] === undefined) { + process.env[k] = env[k]; + } + } + return { + name: "llm-invoke-proxy", + configureServer(server) { + server.middlewares.use("/api/llm-invoke", async (req, res) => { + if (req.method === "OPTIONS") { res.statusCode = 204; res.end(); return; } + if (req.method !== "POST") { res.statusCode = 405; res.end('{"error":"method_not_allowed"}'); return; } + + let raw = ""; + for await (const chunk of req) raw += chunk; + let body: any = {}; + try { body = JSON.parse(raw || "{}"); } + catch { res.statusCode = 400; res.end('{"error":"bad_json"}'); return; } + + const headers: Record = {}; + for (const [k, v] of Object.entries(req.headers)) { + if (typeof v === "string") headers[k] = v; + else if (Array.isArray(v)) headers[k] = v[0] ?? ""; + } + + if (!headers["x-user-api-key"]) { + const provider = typeof body?.provider === "string" ? body.provider : ""; + const fallback = + provider === "gemini" ? env.GEMINI_API_KEY : + provider === "anthropic" ? env.ANTHROPIC_API_KEY : + provider === "openai" ? env.OPENAI_API_KEY : + undefined; + if (fallback) headers["x-user-api-key"] = fallback; + } + + const fakeReq = { method: req.method, headers, body }; + const fakeRes = { + statusCode: 200, + headersSent: false, + _headers: {} as Record, + _body: "" as string, + status(code: number) { this.statusCode = code; this.headersSent = true; return this; }, + setHeader(k: string, v: string) { this._headers[k] = v; return this; }, + json(body: unknown) { this._body = JSON.stringify(body); this._headers["content-type"] = "application/json"; this.headersSent = true; return this; }, + send(body: unknown) { this._body = typeof body === "string" ? body : JSON.stringify(body); this.headersSent = true; return this; }, + write(chunk: Buffer | string) { + this._body += typeof chunk === "string" ? chunk : chunk.toString("utf8"); + this.headersSent = true; + return true; + }, + end(body?: unknown) { + if (body !== undefined) { + this._body += typeof body === "string" ? body : Buffer.isBuffer(body) ? body.toString("utf8") : JSON.stringify(body); + } + return this; + }, + }; + + try { + const mod = await server.ssrLoadModule("/api/llm-invoke.ts"); + await mod.default(fakeReq, fakeRes); + } catch (err) { + if (!res.writableEnded) { + res.statusCode = 500; + res.setHeader("content-type", "application/json"); + res.end(JSON.stringify({ error: "dev_shim_failed", detail: (err as Error).message })); + } + return; + } + + res.statusCode = fakeRes.statusCode; + for (const [k, v] of Object.entries(fakeRes._headers)) res.setHeader(k, v); + res.end(fakeRes._body); + }); + }, + }; +} + // https://vite.dev/config/ export default defineConfig(({ mode }) => { // Vite does NOT auto-populate process.env with .env files at config time. @@ -152,6 +241,7 @@ export default defineConfig(({ mode }) => { injectBridgeCsp(), devExplorerProxy(), llmProxyPlugin(env), + llmInvokeProxyPlugin(env), ], esbuild: { logOverride: { "this-is-undefined-in-esm": "silent" }, @@ -162,7 +252,29 @@ export default defineConfig(({ mode }) => { "process.env": "{}", }, optimizeDeps: { - include: ["ethers", "buffer"], + include: [ + "ethers", + "buffer", + "iframe-shared-storage", + "@cofhe/sdk", + "@cofhe/sdk/web", + "@cofhe/sdk/chains", + ], + exclude: ["tfhe"], + }, + worker: { + format: "es", + }, + test: { + globals: true, + environmentMatchGlobs: [ + ["tests/llm/llmConfig*.test.ts*", "jsdom"], + ["tests/llm/useLlmInvocation*.test.ts*", "jsdom"], + ["tests/llm/consent*.test.ts*", "jsdom"], + ["tests/llm/settings*.test.ts*", "jsdom"], + ["tests/llm/llmSettings*.test.ts*", "jsdom"], + ["tests/llm/destination*.test.ts*", "jsdom"], + ], }, build: { chunkSizeWarningLimit: 1200, @@ -200,12 +312,20 @@ export default defineConfig(({ mode }) => { }, proxy: { // Proxy for EDB bridge (strips /api/edb prefix, forwards to bridge) - // Reads EDB_BRIDGE_URL from .env; falls back to localhost for local bridge dev + // Reads EDB_BRIDGE_URL from .env; falls back to localhost for local bridge dev. + // Injects X-API-Key server-side so the browser never sees the secret — + // mirrors api/edb-proxy.ts behavior for local dev. "/api/edb": { - target: process.env.EDB_BRIDGE_URL || "http://127.0.0.1:5789", + target: env.EDB_BRIDGE_URL || "http://127.0.0.1:5789", changeOrigin: true, secure: true, rewrite: (path) => path.replace(/^\/api\/edb/, ""), + configure: (proxy) => { + const apiKey = env.EDB_API_KEY || ""; + proxy.on("proxyReq", (proxyReq) => { + if (apiKey) proxyReq.setHeader("X-API-Key", apiKey); + }); + }, }, // Proxy for Sourcify Repository API (must be BEFORE the general /api/sourcify) // repo.sourcify.dev now 307-redirects to sourcify.dev/server/repository,