From 213c8c4dcae918d58f06ef23663f24b88fa7b5f8 Mon Sep 17 00:00:00 2001 From: leafwithered Date: Fri, 21 Aug 2026 16:02:44 +0800 Subject: [PATCH 1/5] feat: add OKX simulation evidence preview --- .env.example | 4 + .github/workflows/ci.yml | 3 +- README.md | 71 ++++++++--- app/globals.css | 34 ++++- app/page.tsx | 92 +++++++++++--- docs/DEPLOYMENT.md | 10 ++ docs/TODO_USER.md | 10 ++ docs/V4_OKX_SIMULATION.md | 95 ++++++++++++++ lib/ai/provider.ts | 2 +- lib/analysis-state.ts | 4 +- lib/analyze-pipeline.ts | 51 ++++++-- lib/chain/intelligence.ts | 12 +- lib/consequence.ts | 40 +++++- lib/evidence.ts | 69 +++++++++- lib/network.ts | 42 +++++++ lib/okx/simulation.ts | 244 ++++++++++++++++++++++++++++++++++++ lib/risk.ts | 5 +- package.json | 1 + test/ai-provider.ts | 2 + test/analysis-state.ts | 6 +- test/analyze-pipeline.ts | 2 + test/consequence.ts | 6 +- test/intelligence.ts | 2 +- test/judge-presets.ts | 2 + test/network.ts | 33 +++++ test/okx-simulation.ts | 124 ++++++++++++++++++ test/security-benchmark.ts | 6 +- test/semantic-hardening.ts | 2 +- test/simulation-pipeline.ts | 78 ++++++++++++ 29 files changed, 980 insertions(+), 72 deletions(-) create mode 100644 docs/V4_OKX_SIMULATION.md create mode 100644 lib/network.ts create mode 100644 lib/okx/simulation.ts create mode 100644 test/network.ts create mode 100644 test/okx-simulation.ts create mode 100644 test/simulation-pipeline.ts diff --git a/.env.example b/.env.example index 77a7b1b..30c3437 100644 --- a/.env.example +++ b/.env.example @@ -2,5 +2,9 @@ AI_API_KEY= AI_BASE_URL= AI_MODEL= XLAYER_RPC_URL=https://testrpc.xlayer.tech/terigon +XLAYER_MAINNET_RPC_URL=https://rpc.xlayer.tech +OKX_API_KEY= +OKX_SECRET_KEY= +OKX_API_PASSPHRASE= NEXT_PUBLIC_RISK_REGISTRY_ADDRESS= DEPLOYER_PRIVATE_KEY= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a0f7bc..7587150 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [main, enhancement-v2, final-judge-upgrade, final-polish, v3-competition] + branches: [main, enhancement-v2, final-judge-upgrade, final-polish, v3-competition, codex/v4-okx-simulation] pull_request: branches: [main] @@ -35,3 +35,4 @@ jobs: - run: pnpm run pipeline:test - run: pnpm run token-standard:test - run: pnpm run security-benchmark:test + - run: pnpm run simulation:test diff --git a/README.md b/README.md index 546beda..3686c05 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,22 @@ XGuard AI decodes transaction behavior, explains the deterministic consequences **Demo asset:** `demo/xguard-ai-build-x-demo.mp4` (1920×1080 H.264, approximately 1:34). It was composed from verified screenshots of the canonical Production deployment and demonstrates Judge Mode, Hybrid Analysis, Risk Fusion, real X Layer RPC intelligence, and the existing confirmed receipt without connecting a wallet or creating a transaction. -## 60–90 Second Judge Path (V3.1 candidate) +## V4 Preview Candidate — OKX Simulation Evidence + +The `codex/v4-okx-simulation` branch extends the frozen V3 evidence architecture without changing the stable Production deployment: + +- **X Layer Testnet (`1952`)** keeps the existing bounded RPC intelligence and preflight path. It never calls the Mainnet simulator. +- **X Layer Mainnet (`196`, OKX `chainIndex: "196"`)** adds the official OKX OnchainOS Transaction Simulation API as a server-side, read-only evidence provider. +- RPC preflight and OKX simulation remain separate evidence sources. Any disagreement is surfaced explicitly and caps analysis confidence. +- Simulation facts enter normalized evidence before the optional AI call. AI may explain those facts but cannot rewrite them or lower the deterministic safety floor. +- An empty OKX risk list means only **“No OKX simulation risk entries were returned.”** It is not proof of safety. +- Missing credentials, authentication errors, rate limits, timeouts, malformed responses, and provider errors remain visible but non-fatal; deterministic/RPC/AI-or-Local analysis continues. + +This V4 code is a **Preview candidate only** until a real read-only API verification and separate release approval are complete. It does not alter the existing RiskRegistry, verified receipt, canonical Production URL, or Production environment. + +See [docs/V4_OKX_SIMULATION.md](docs/V4_OKX_SIMULATION.md) for the evidence schema, trust boundaries, failure matrix, and credential handoff gate. + +## 60–90 Second Judge Path (V3 stable) 1. Open the V3.1 Preview and select **⚡ Try Judge Demo**. 2. Load **Safe Transfer**, then explicitly click **Analyze risk** to see the LOW baseline. @@ -34,7 +49,7 @@ Judge Mode only loads examples, navigates, and explains. It never auto-analyzes, ## Stable Production Baseline -The verified Production baseline is commit `8f37ee568ee02cb7affa51069eac618c3adb9363` and remains live on the canonical Vercel Production URL. Production Hybrid Analysis is verified through the provider-neutral adapter; the upstream provider is selected only through server-side environment variables and is not asserted by the public client. The V1 RiskRegistry evidence and every public URL remain unchanged. +The frozen V3 stable checkpoint is commit `04575cc764163c7cb99b948c050e974e4cd20a2e` with tag `v3.1.1-stable`, and the canonical Vercel Production URL remains unchanged. Production Hybrid Analysis is verified through the provider-neutral adapter; the upstream provider is selected only through server-side environment variables and is not asserted by the public client. The V1 RiskRegistry evidence and every public URL remain unchanged. - Safe Transfer: `8 LOW`, Hybrid Analysis - Unlimited Approval: `72 HIGH`, decoded ERC20 `approve`, spender and `Amount: Unlimited` visible @@ -42,9 +57,9 @@ The verified Production baseline is commit `8f37ee568ee02cb7affa51069eac618c3adb - Clear Analysis, wallet connection, X Layer Testnet switching, and explicit user confirmation are included - Contract V2 is documented as a proposal only; no new contract or chain transaction was introduced -### V3 Preview Candidate +### V3 Stable Evidence Architecture -The `v3-competition` branch adds an evidence-first analysis pipeline, deterministic Transaction Consequence Engine, optional Intent vs Reality comparison, selector-ambiguity hardening, and a reproducible 57-case security benchmark (34 corpus cases, 11 pipeline invariants, and 12 semantic/adversarial cases). It is developed and deployed as a Preview candidate only until explicit approval; it does not alter the existing Production deployment, RiskRegistry contract, or verified receipt. +V3 adds an evidence-first analysis pipeline, deterministic Transaction Consequence Engine, optional Intent vs Reality comparison, selector-ambiguity hardening, and a reproducible 57-case security benchmark (34 corpus cases, 11 pipeline invariants, and 12 semantic/adversarial cases). The stable checkpoint does not alter the existing RiskRegistry contract or verified receipt. ### V3.1 semantic correctness @@ -88,12 +103,13 @@ The hybrid design combines a deterministic Risk Engine with a configurable OpenA 2. Detect or switch to X Layer Testnet (`1952`). 3. Enter `from`, `to`, value, calldata, and an optional plain-language expectation. 4. Decode supported calldata and run the deterministic Local Risk Engine. -5. Inspect the target through real X Layer RPC calls and run bounded `eth_call` / `eth_estimateGas` preflight checks—not a full state-diff simulation. -6. Build sanitized, normalized evidence containing decoded behavior, deterministic signals, consequences, contract facts, and execution facts. -7. Derive confidence, verdict, and execution state deterministically, then call the optional AI adapter once with that evidence. -8. Compare optional stated intent with decoded reality, enforce deterministic mismatch floors, and fuse AI advisory risk without allowing it to lower the floor. -9. Review consequences, Intent vs Reality, known-risk severity, confidence, execution, source-labeled signals, intelligence, reasons, and recommendation before signing. -10. Optionally confirm explicitly and record the assessment hash and score through `RiskRegistry`. +5. Inspect the target through the RPC selected for the explicit analysis network and run bounded `eth_call` / `eth_estimateGas` preflight checks. +6. On X Layer Mainnet only, request additional read-only transaction evidence from OKX OnchainOS Transaction Simulation; X Layer Testnet never uses this API. +7. Build sanitized, normalized evidence containing decoded behavior, deterministic signals, consequences, contract facts, RPC execution facts, and the immutable simulation block. +8. Derive confidence, verdict, execution state, and evidence consistency deterministically, then call the optional AI adapter once with that evidence. +9. Compare optional stated intent with decoded reality, enforce deterministic mismatch floors, and fuse AI advisory risk without allowing it to lower the floor. +10. Review consequences, Intent vs Reality, known-risk severity, confidence, separate RPC/simulation evidence, reasons, and recommendation before signing. +11. Optionally confirm explicitly and record the assessment hash and score through the existing Testnet `RiskRegistry`. ## Architecture @@ -103,8 +119,11 @@ flowchart TD D --> L[Deterministic Risk Engine] L --> CI[On-chain Contract Intelligence] CI --> P[Transaction Preflight] - P --> C[Deterministic Consequences] - C --> E[Normalized Evidence] + P --> O{Mainnet 196?} + O -->|yes| X[OKX OnchainOS Simulation] + O -->|no: Testnet 1952| C[Deterministic Consequences] + X --> C + C --> E[Normalized Immutable Evidence] E --> V[Confidence / Verdict / Execution] E --> A[One AI Advisory Call] I[Optional User Intent] --> M[Intent vs Reality] @@ -122,7 +141,7 @@ flowchart TD ## AI Risk Engine -`lib/analyze-pipeline.ts` owns the evidence-first orchestration and accepts small injectable RPC and AI dependencies for integration testing. `lib/evidence.ts` creates a bounded, sanitized object with the transaction, decoded action, deterministic signals, consequences, address type, code presence/size, scoped EIP-1967 observation, preflight status/reason, gas estimate, and RPC status. Only after that evidence exists does `lib/ai/provider.ts` make one advisory request. Configure an OpenAI-compatible provider using `AI_API_KEY`, `AI_BASE_URL`, and `AI_MODEL`. The adapter attempts `/v1/responses` first and then `/v1/chat/completions`. Output is validated before use. Production Hybrid Analysis is verified through this server-configured adapter; public artifacts do not assert the upstream provider identity. +`lib/analyze-pipeline.ts` owns the evidence-first orchestration and accepts small injectable RPC, simulation, and AI dependencies for integration testing. `lib/evidence.ts` creates a bounded, sanitized object with the transaction, decoded action, deterministic signals, consequences, address type, code presence/size, scoped EIP-1967 observation, RPC preflight facts, and normalized OKX simulation evidence. Only after that evidence exists does `lib/ai/provider.ts` make one advisory request. Configure an OpenAI-compatible provider using `AI_API_KEY`, `AI_BASE_URL`, and `AI_MODEL`. The adapter attempts `/v1/responses` first and then `/v1/chat/completions`. Output is validated before use. Production Hybrid Analysis is verified through this server-configured adapter; public artifacts do not assert the upstream provider identity. Confidence and verdict are deterministic: unsupported, malformed, or materially token-standard-ambiguous calldata is `LOW` confidence and `UNDETERMINED`; a known decode with unavailable or partial RPC evidence is generally `MEDIUM`; a known decode with complete RPC evidence may be `HIGH`; an observed EIP-1967 implementation caps confidence at `MEDIUM` because arbitrary implementation behavior is not fully inspected. Every response includes deterministic `confidenceReasons`. High risk does not mean low confidence, and preflight revert does not add arbitrary malicious-risk points. AI cannot change evidence, execution status, verdict, or final analysis confidence. @@ -138,11 +157,12 @@ The adapter is intentionally provider-neutral: `AI_BASE_URL` may point at a thir ## X Layer Integration -- Network: X Layer Testnet -- Chain ID: `1952` -- Native token: `OKB` -- Official RPC: `https://testrpc.xlayer.tech/terigon` -- Explorer: `https://www.okx.com/web3/explorer/xlayer-test` +| Analysis network | Chain ID | RPC evidence | OKX Transaction Simulation | +| --- | ---: | --- | --- | +| X Layer Testnet | `1952` | `https://testrpc.xlayer.tech/terigon` | Unsupported; never called | +| X Layer Mainnet | `196` | `https://rpc.xlayer.tech` | Supported in V4 with `chainIndex: "196"` | + +Both networks use OKB as the native token. The existing `RiskRegistry` and verified receipt remain Testnet-only. ### On-chain Intelligence and Preflight @@ -155,6 +175,12 @@ For each valid destination, the server performs isolated, timeout-bounded X Laye RPC failure never blocks deterministic analysis. Unavailable results are labeled `Unavailable`; XGuard does not invent contract reputation or simulation output. The Transaction Analyzer can also load a real X Layer transaction and receipt for clearly labeled **post-hoc** analysis. +### OKX OnchainOS Transaction Simulation (V4 Preview) + +For an explicitly selected X Layer Mainnet analysis, the server signs one bounded `POST /api/v6/dex/pre-transaction/simulate` request using the exact raw JSON body sent on the wire. `txAmount` is converted from human OKB to 18-decimal base units, and calldata is sent unchanged. The UI preserves provider-returned intention, asset changes and raw signed values, gas used, failure reason, and risk entries. A simulation failure means the transaction may fail under the simulated state; it is not automatically labeled malicious. + +Credentials are server-side only. The browser bundle, API response, logs, fixtures, and repository never receive keys, passphrases, or authentication signatures. Phase A intentionally performs no real OKX request; real verification is gated on Preview-only credential configuration. + ## Smart Contract `contracts/RiskRegistry.sol` stores an assessment by `analysisHash` with the submitting user, risk score, and timestamp. It holds no funds and creates no token. @@ -205,6 +231,10 @@ The public production deployment is available at https://xguard-ai-six.vercel.ap | `AI_BASE_URL` | OpenAI-compatible provider base URL, with or without `/v1` | | `AI_MODEL` | Provider-specific model identifier | | `XLAYER_RPC_URL` | X Layer Testnet deployment RPC | +| `XLAYER_MAINNET_RPC_URL` | X Layer Mainnet RPC used for Mainnet intelligence/preflight | +| `OKX_API_KEY` | OKX OnchainOS API key; server-side only | +| `OKX_SECRET_KEY` | OKX signing secret; server-side only | +| `OKX_API_PASSPHRASE` | OKX API passphrase; server-side only | | `NEXT_PUBLIC_RISK_REGISTRY_ADDRESS` | Deployed registry address used by the browser | | `DEPLOYER_PRIVATE_KEY` | Deployment wallet key; never commit | @@ -237,6 +267,7 @@ pnpm run intent:test pnpm run pipeline:test pnpm run token-standard:test pnpm run security-benchmark:test +pnpm run simulation:test ``` The full 57-case V3.1 benchmark and its invariants are documented in [docs/SECURITY_BENCHMARK.md](docs/SECURITY_BENCHMARK.md). @@ -255,7 +286,7 @@ XGuard AI is an advisory prototype, not an audit, wallet firewall, or guarantee ## Limitations -- Transaction Preflight uses `eth_call` and `eth_estimateGas`; it is not a full state-diff simulation and does not prove contract safety. +- RPC preflight uses `eth_call` and `eth_estimateGas`. V4 adds bounded OKX simulation evidence on Mainnet, but does not claim full state-diff coverage or proof of safety. - Contract reputation and verified source metadata are not yet integrated. - The on-chain registry stores the submitting address, score, hash, and timestamp only; it does not execute or protect transactions. - AI provider behavior depends on the configured service and its OpenAI-compatible endpoint behavior. @@ -265,7 +296,7 @@ XGuard AI is an advisory prototype, not an audit, wallet firewall, or guarantee - Wallet SDK/API integration for other X Layer applications. - Browser extension and wallet-native pre-sign delivery after the web prototype. - Verified source metadata and bytecode provenance from authoritative sources. -- Full state-diff simulation with explicit trace provenance. +- Additional trace/state-diff provenance only if an authoritative provider exposes those fields explicitly. - Evidence-backed phishing intelligence without fabricated reputation scores. - X Layer mainnet deployment only after the required testnet phase and a separate security review. diff --git a/app/globals.css b/app/globals.css index f639f3e..e66ccab 100644 --- a/app/globals.css +++ b/app/globals.css @@ -65,8 +65,9 @@ h1 { font-size: clamp(44px, 7vw, 78px); line-height: .94; margin: 16px 0 18px; m .presets strong, .presets span { display: block; } .presets span { color: var(--muted); font-size: 11px; line-height: 1.35; margin-top: 6px; } label { display: block; color: var(--muted); font-size: 13px; margin: 18px 0 8px; } -textarea, input { width: 100%; background: var(--panel-soft); border: 1px solid var(--line); border-radius: 12px; color: var(--text); padding: 13px; outline: none; } -textarea:focus, input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(119,242,194,.08); } +textarea, input, select { width: 100%; background: var(--panel-soft); border: 1px solid var(--line); border-radius: 12px; color: var(--text); padding: 13px; outline: none; } +textarea:focus, input:focus, select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(119,242,194,.08); } +select { color-scheme: dark; } textarea { min-height: 110px; resize: vertical; } .field-note { margin-top: 7px; color: var(--muted); font-size: 11px; line-height: 1.4; } .row { display: grid; grid-template-columns: .7fr 1.3fr; gap: 12px; } @@ -94,14 +95,36 @@ textarea { min-height: 110px; resize: vertical; } .assessment-dimensions strong { font-size: 12px; word-break: break-word; } .assessment-dimensions.verdict-undetermined { border-left: 3px solid var(--warning); padding-left: 8px; } .confidence-reasons { margin: 10px 0 0; padding-left: 18px; color: var(--muted); font-size: 11px; line-height: 1.55; } -.decoded-card, .intelligence-card, .fusion-card, .consequence-card, .intent-card, .explanation, .record-card { margin-top: 18px; background: var(--panel-soft); border: 1px solid var(--line); border-radius: 14px; padding: 16px; } -.decoded-card h3, .intelligence-card h3, .fusion-card h3, .consequence-card h3, .intent-card h3, .signal-section h3, .explanation h3 { margin: 0 0 10px; font-size: 14px; } +.decoded-card, .intelligence-card, .simulation-card, .timing-card, .fusion-card, .consequence-card, .intent-card, .explanation, .record-card { margin-top: 18px; background: var(--panel-soft); border: 1px solid var(--line); border-radius: 14px; padding: 16px; } +.decoded-card h3, .intelligence-card h3, .simulation-card h3, .timing-card h3, .fusion-card h3, .consequence-card h3, .intent-card h3, .signal-section h3, .explanation h3 { margin: 0 0 10px; font-size: 14px; } .decoded-grid { display: grid; grid-template-columns: 96px minmax(0,1fr); gap: 8px 12px; font-size: 12px; } .decoded-grid span { color: var(--muted); } .decoded-grid strong { word-break: break-all; } .decoded-card p, .intelligence-card p, .fusion-card p, .explanation p { margin-bottom: 0; font-size: 13px; } .intelligence-card { border-color: rgba(119,242,194,.18); } .intelligence-card code { color: var(--accent); } +.simulation-card { border-color: rgba(181,166,255,.3); } +.simulation-card > p { color: var(--muted); font-size: 12px; line-height: 1.5; } +.simulation-status { border: 1px solid var(--line); border-radius: 999px; padding: 5px 8px; color: var(--muted); font-size: 10px; } +.simulation-available { border-color: rgba(181,166,255,.45); color: #b5a6ff; } +.simulation-error { border-color: rgba(255,124,139,.45); color: var(--danger); } +.simulation-detail { padding: 10px; border-radius: 9px; background: rgba(8,11,18,.45); } +.simulation-subsection { margin-top: 14px; display: grid; gap: 8px; } +.simulation-subsection > strong { font-size: 12px; } +.simulation-subsection > p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.45; } +.simulation-list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; } +.simulation-list li { display: grid; gap: 5px; padding: 10px; border-radius: 9px; background: rgba(8,11,18,.45); } +.simulation-list li > div { display: flex; justify-content: space-between; gap: 8px; color: var(--muted); font-size: 10px; } +.simulation-list p, .simulation-list code, .simulation-list small { margin: 0; overflow-wrap: anywhere; } +.simulation-list p { font-size: 12px; } +.simulation-list code, .simulation-list small { color: var(--muted); font-size: 10px; } +.evidence-inconsistency { margin-top: 14px; padding: 11px; border-left: 3px solid var(--warning); border-radius: 9px; background: rgba(249,198,107,.08); } +.evidence-inconsistency strong { color: var(--warning); font-size: 12px; } +.evidence-inconsistency ul { margin: 7px 0 0; padding-left: 18px; color: var(--muted); font-size: 11px; } +.timing-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 8px; } +.timing-grid div { display: grid; gap: 4px; padding: 10px; border-radius: 9px; background: rgba(8,11,18,.45); } +.timing-grid span { color: var(--muted); font-size: 9px; text-transform: uppercase; } +.timing-grid strong { font-size: 12px; } .card-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } .card-title .eyebrow { display: block; margin-bottom: 7px; font-size: 10px; } .rpc-status { border: 1px solid var(--line); border-radius: 999px; padding: 5px 8px; color: var(--muted); font-size: 10px; } @@ -142,6 +165,7 @@ textarea { min-height: 110px; resize: vertical; } .source-rule { color: var(--danger); background: rgba(255,124,139,.1); } .source-decoder { color: var(--warning); background: rgba(249,198,107,.1); } .source-on-chain { color: var(--accent); background: rgba(119,242,194,.1); } +.source-okx { color: #b5a6ff; background: rgba(181,166,255,.1); } .safety-guarantee { display: grid; gap: 8px; margin-top: 18px; border: 1px solid rgba(119,242,194,.24); border-radius: 14px; padding: 15px; background: rgba(119,242,194,.06); } .safety-guarantee strong { font-size: 13px; line-height: 1.45; } .recommendation { background: rgba(119,242,194,.07); border: 1px solid rgba(119,242,194,.18); border-radius: 12px; padding: 14px; } @@ -183,7 +207,7 @@ textarea { min-height: 110px; resize: vertical; } .panel-heading > button { width: 100%; } .presets, .row, .why-xlayer, .tx-search, .judge-steps { grid-template-columns: 1fr; } .capability-strip { grid-template-columns: repeat(2, 1fr); } - .fusion-grid, .assessment-dimensions { grid-template-columns: 1fr; } + .fusion-grid, .assessment-dimensions, .timing-grid { grid-template-columns: 1fr; } .presets button { min-height: 0; } .score { width: 88px; height: 88px; flex-basis: 88px; } .record-card > div:first-child { display: grid; } diff --git a/app/page.tsx b/app/page.tsx index 4c80ef1..3dd942c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,13 +1,15 @@ "use client"; import { useEffect, useLayoutEffect, useMemo, useReducer, useState } from "react"; -import { createPublicClient, createWalletClient, custom, http, isAddress, keccak256, toHex, type Address } from "viem"; +import { createPublicClient, createWalletClient, custom, formatUnits, http, isAddress, keccak256, toHex, type Address } from "viem"; import { riskRegistryAbi, xLayerTestnet } from "../lib/xlayer"; import type { RiskInput, RiskResult } from "../lib/risk"; import type { ContractIntelligence } from "../lib/chain/intelligence"; import type { TransactionConsequence } from "../lib/consequence"; import type { IntentComparison } from "../lib/intent"; import type { AnalysisConfidence, AnalysisVerdict, ExecutionStatus } from "../lib/evidence"; +import { getAnalysisNetworkConfig, type AnalysisNetwork } from "../lib/network"; +import type { SimulationEvidence } from "../lib/okx/simulation"; import type { XLayerTransaction } from "../lib/chain/transaction-analyzer"; import { judgePresets as presets } from "../lib/presets"; import { currentAnalysisResult, invalidateStaleAnalysis } from "../lib/analysis-state"; @@ -29,15 +31,25 @@ type AnalysisResult = RiskResult & { contractIntelligence: ContractIntelligence; consequences: TransactionConsequence[]; intentComparison: IntentComparison; + analysisTimings: { rpcMs: number; simulationMs: number; aiMs: number; totalMs: number }; + simulationEvidence: SimulationEvidence; + evidenceConsistency: { status: "CONSISTENT" | "INCONSISTENT" | "NOT_COMPARABLE"; reasons: string[] }; }; const shortAddress = (value: string) => value ? `${value.slice(0, 6)}…${value.slice(-4)}` : ""; -const signalSources = new Set(["RULE", "DECODER", "ON-CHAIN", "AI"]); -const consequenceSources = new Set(["DECODER", "VALUE", "ON_CHAIN"]); +const signalSources = new Set(["RULE", "DECODER", "ON-CHAIN", "OKX", "AI"]); +const consequenceSources = new Set(["DECODER", "VALUE", "ON_CHAIN", "SIMULATION"]); const intentStatuses = new Set(["MATCH", "PARTIAL", "MISMATCH", "UNKNOWN"]); const analysisConfidences = new Set(["HIGH", "MEDIUM", "LOW"]); const analysisVerdicts = new Set(["ASSESSED", "UNDETERMINED"]); const executionStatuses = new Set(["SUCCEEDED", "REVERTED", "UNAVAILABLE"]); +const simulationStatuses = new Set(["AVAILABLE", "UNAVAILABLE", "UNSUPPORTED", "ERROR"]); +const consistencyStatuses = new Set(["CONSISTENT", "INCONSISTENT", "NOT_COMPARABLE"]); + +function formatSimulationAmount(asset: SimulationEvidence["assetChanges"][number]) { + if ((asset.assetType !== "NATIVE" && asset.assetType !== "ERC20") || !Number.isInteger(asset.decimals) || asset.decimals === null || asset.decimals < 0 || asset.decimals > 36) return null; + try { return formatUnits(BigInt(asset.rawValue), asset.decimals); } catch { return null; } +} function isCurrentAnalysisResult(value: unknown): value is AnalysisResult { if (!value || typeof value !== "object") return false; @@ -45,6 +57,15 @@ function isCurrentAnalysisResult(value: unknown): value is AnalysisResult { const validSignals = (signals: unknown) => Array.isArray(signals) && signals.every((item) => item && typeof item === "object" && signalSources.has(String((item as { source?: unknown }).source))); const validConsequences = Array.isArray(candidate.consequences) && candidate.consequences.every((item) => item && typeof item === "object" && consequenceSources.has(String((item as { evidenceSource?: unknown }).evidenceSource))); const validIntent = Boolean(candidate.intentComparison && intentStatuses.has(String(candidate.intentComparison.status))); + const validTimings = Boolean(candidate.analysisTimings && [candidate.analysisTimings.rpcMs, candidate.analysisTimings.simulationMs, candidate.analysisTimings.aiMs, candidate.analysisTimings.totalMs].every((duration) => typeof duration === "number" && Number.isFinite(duration) && duration >= 0)); + const validSimulation = Boolean(candidate.simulationEvidence + && simulationStatuses.has(String(candidate.simulationEvidence.status)) + && Array.isArray(candidate.simulationEvidence.assetChanges) + && Array.isArray(candidate.simulationEvidence.risks)); + const validConsistency = Boolean(candidate.evidenceConsistency + && consistencyStatuses.has(String(candidate.evidenceConsistency.status)) + && Array.isArray(candidate.evidenceConsistency.reasons) + && candidate.evidenceConsistency.reasons.every((reason) => typeof reason === "string")); return typeof candidate.finalScore === "number" && typeof candidate.deterministicScore === "number" && analysisConfidences.has(String(candidate.analysisConfidence)) @@ -56,7 +77,10 @@ function isCurrentAnalysisResult(value: unknown): value is AnalysisResult { && validSignals(candidate.advisorySignals) && Boolean(candidate.contractIntelligence) && validConsequences - && validIntent; + && validIntent + && validTimings + && validSimulation + && validConsistency; } export default function Home() { @@ -69,6 +93,7 @@ export default function Home() { const [value, setValue] = useState("0"); const [data, setData] = useState("0x"); const [context, setContext] = useState(""); + const [analysisNetwork, setAnalysisNetwork] = useState("XLAYER_TESTNET"); const [result, setResult] = useState(null); const [lastInput, setLastInput] = useState(null); const [analyzing, setAnalyzing] = useState(false); @@ -80,10 +105,12 @@ export default function Home() { const [transactionLoading, setTransactionLoading] = useState(false); const [transactionError, setTransactionError] = useState(""); const [judgeModeOpen, setJudgeModeOpen] = useState(false); - const networkName = chainId === null ? "Not connected" : chainId === 1952 ? "X Layer Testnet" : `Wrong network · ${chainId}`; + const walletNetworkName = chainId === null ? "Wallet not connected" : chainId === 1952 ? "Wallet on X Layer Testnet" : `Wallet network · ${chainId}`; + const analysisNetworkConfig = getAnalysisNetworkConfig(analysisNetwork); + const networkName = analysisNetworkConfig.name; const isCorrectNetwork = chainId === 1952; const recordPending = isRecordPending(recordState); - const currentTransactionInput: RiskInput = { from, to, value, data, context }; + const currentTransactionInput: RiskInput = { from, to, value, data, context, analysisNetwork }; const activeResult = currentAnalysisResult({ result, lastInput, reviewed }, currentTransactionInput); const analysisHash = useMemo(() => lastInput && activeResult ? keccak256(toHex(JSON.stringify({ input: lastInput, result: activeResult }))) : null, [lastInput, activeResult]); @@ -101,13 +128,14 @@ export default function Home() { setValue(parsed.input.value); setData(parsed.input.data); setContext(parsed.input.context); + setAnalysisNetwork(parsed.input.analysisNetwork ?? "XLAYER_TESTNET"); } catch { window.sessionStorage.removeItem("xguard-session-result"); } }, []); useLayoutEffect(() => { - const currentInput: RiskInput = { from, to, value, data, context }; + const currentInput: RiskInput = { from, to, value, data, context, analysisNetwork }; const freshness = invalidateStaleAnalysis({ result, lastInput, reviewed }, currentInput); if (!freshness.invalidated) return; setResult(freshness.snapshot.result); @@ -116,7 +144,7 @@ export default function Home() { dispatchRecord({ type: "RESET" }); window.sessionStorage.removeItem("xguard-session-result"); setMessage(freshness.notice); - }, [from, to, value, data, context, result, lastInput, reviewed]); + }, [from, to, value, data, context, analysisNetwork, result, lastInput, reviewed]); useEffect(() => { const discovered = new Map(); @@ -198,6 +226,7 @@ export default function Home() { setValue(input.value); setData(input.data); setContext(input.context); + setAnalysisNetwork(input.analysisNetwork ?? "XLAYER_TESTNET"); } function clearAnalysis(clearFields = true) { @@ -212,7 +241,7 @@ export default function Home() { async function analyze() { setMessage(""); setReviewed(false); dispatchRecord({ type: "RESET" }); - const input: RiskInput = { from, to, value, data, context }; + const input: RiskInput = { from, to, value, data, context, analysisNetwork }; if (!isAddress(to)) { setMessage("Enter a valid recipient contract address."); return; } setAnalyzing(true); try { @@ -230,7 +259,7 @@ export default function Home() { } async function recordOnchain() { - if (!registryAddress || !analysisHash || !walletProvider || !address || !activeResult || !isCorrectNetwork || !reviewed || recordPending) return; + if (!registryAddress || !analysisHash || !walletProvider || !address || !activeResult || analysisNetwork !== "XLAYER_TESTNET" || !isCorrectNetwork || !reviewed || recordPending) return; setMessage(""); dispatchRecord({ type: "SIGNATURE_REQUESTED" }); try { @@ -283,18 +312,19 @@ export default function Home() { const recordLabel = recordState.phase === "awaiting-signature" ? "Awaiting wallet signature" : recordState.phase === "submitted" ? "Submitted" : recordState.phase === "confirming" ? "Confirming on X Layer" : recordState.phase === "confirmed" ? "Confirmed on X Layer" : recordState.phase === "reverted" ? "Transaction reverted" : recordState.phase === "error" ? "Confirmation error" : "Ready after review"; const decoded = activeResult?.decodedAction; const intelligence = activeResult?.contractIntelligence; + const simulation = activeResult?.simulationEvidence; return
XXGuard AI
-
{isCorrectNetwork && }{networkName} · Chain 1952
+
Analysis: {networkName} · Chain {analysisNetworkConfig.chainId}
Explainable pre-sign intelligence

The pre-sign security layer for X Layer.

Decode transactions, inspect on-chain context, apply deterministic safety rules, and use AI without allowing AI to override known security signals.

-

What happens if I sign this?

Network{networkName}
Analysis{modeLabel}
Safety floorDeterministic
SigningAlways user-confirmed
+

What happens if I sign this?

Analysis Network{networkName}
Wallet{walletNetworkName}
Analysis{modeLabel}
Safety floorDeterministic
SigningAlways user-confirmed
-
Transaction DecoderOn-chain IntelligenceDeterministic Safety FloorAI Risk Analysis
+
Transaction DecoderChain-aware RPCOKX Simulation EvidenceDeterministic + AI Fusion
{judgeModeOpen &&
60-Second Judge Path

See why XGuard is more than an AI wrapper.

Each action is explicit. Nothing connects, signs, records, or broadcasts automatically.

@@ -309,12 +339,14 @@ export default function Home() {

1. Prepare transaction

Start with a preset or inspect a transaction manually.

{presets.map((preset) => )}
+ +
Testnet never uses the Mainnet simulator. Mainnet simulation is read-only evidence and requires a sender address.
setFrom(event.target.value)} /> setTo(event.target.value)} />
setValue(event.target.value)} />
setData(event.target.value)} />