diff --git a/.github/workflows/gas-regression.yml b/.github/workflows/gas-regression.yml
index 398d974..ac45c23 100644
--- a/.github/workflows/gas-regression.yml
+++ b/.github/workflows/gas-regression.yml
@@ -10,11 +10,11 @@
# 6. Fails CI if any threshold in atupa.toml is exceeded
#
# REQUIRED SECRETS (in repo Settings → Secrets):
-# ATUPA_RPC_URL — Optional: override Anvil with a real mainnet fork RPC
+# ATUPA_RPC_URL — Optional: override Anvil with a real RPC endpoint
#
-# REQUIRED REPO VARIABLES:
-# PROFILE_SCRIPT — relative path to a forge/cast script that outputs a TX hash
-# to stdout (default: script/ProfileScript.s.sol)
+# REPO VARIABLES (optional):
+# PROFILE_SCRIPT — relative path to a forge/cast script (default: script/ProfileScript.s.sol)
+# ATUPA_PROTOCOL — optional protocol adapter (aave | lido)
# ─────────────────────────────────────────────────────────────────────────────
name: ⛽ Atupa Gas Regression
@@ -22,14 +22,19 @@ name: ⛽ Atupa Gas Regression
on:
pull_request:
branches: [ main ]
- # Allow manual trigger for debugging
workflow_dispatch:
inputs:
base_tx:
- description: 'Override base transaction hash'
+ description: 'Override base transaction hash / signature'
required: false
target_tx:
- description: 'Override target transaction hash'
+ description: 'Override target transaction hash / signature'
+ required: false
+ protocol:
+ description: 'Optional protocol deep diff (aave | lido)'
+ required: false
+ vm:
+ description: 'Explicit VM runtime (evm | stylus | starknet | solana | stellar)'
required: false
permissions:
@@ -38,7 +43,6 @@ permissions:
env:
CARGO_TERM_COLOR: always
- # Use Anvil by default; override with a real RPC via secret for mainnet-fork
RPC_URL: ${{ secrets.ATUPA_RPC_URL || 'http://127.0.0.1:8545' }}
ANVIL_PORT: 8545
ANVIL_MNEMONIC: 'test test test test test test test test test test test junk'
@@ -78,7 +82,6 @@ jobs:
--steps-tracing \
--silent &
echo "ANVIL_PID=$!" >> $GITHUB_ENV
- # Wait for Anvil to be ready
for i in $(seq 1 20); do
cast block-number --rpc-url http://127.0.0.1:${{ env.ANVIL_PORT }} &>/dev/null && break
sleep 0.5
@@ -94,7 +97,6 @@ jobs:
PROFILE_SCRIPT="${{ vars.PROFILE_SCRIPT || 'script/ProfileScript.s.sol' }}"
if [ -f "$PROFILE_SCRIPT" ]; then
- # Forge deployment path: run script, capture TX hash from stdout
TX_HASH=$(forge script "$PROFILE_SCRIPT" \
--rpc-url http://127.0.0.1:${{ env.ANVIL_PORT }} \
--private-key $PRIVATE_KEY \
@@ -102,7 +104,6 @@ jobs:
--json 2>/dev/null \
| jq -r '.receipts[-1].transactionHash' 2>/dev/null)
else
- # Fallback: Use a simple cast send to a known deployed contract
echo "⚠️ No PROFILE_SCRIPT found. Sending dummy ETH transfer for baseline."
TX_HASH=$(cast send \
--rpc-url http://127.0.0.1:${{ env.ANVIL_PORT }} \
@@ -141,7 +142,6 @@ jobs:
with:
key: atupa-ci-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
-
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
@@ -197,10 +197,9 @@ jobs:
diff:
name: 🏮 Atupa Gas Diff
runs-on: ubuntu-latest
- # Support manual override of hashes via workflow_dispatch
needs: [ baseline, target ]
permissions:
- pull-requests: write # Required for posting PR comments
+ pull-requests: write
steps:
- name: Checkout PR branch
@@ -214,7 +213,6 @@ jobs:
with:
key: atupa-ci-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
-
- name: Install Foundry (for Anvil)
uses: foundry-rs/foundry-toolchain@v1
@@ -245,16 +243,19 @@ jobs:
echo " Base: $BASE"
echo " Target: $TARGET"
- # Use the Atupa composite action (the action.yml we just created)
+ # Execute Atupa composite action
- name: Run Atupa Gas Diff
uses: ./
with:
base_tx: ${{ steps.hashes.outputs.base }}
target_tx: ${{ steps.hashes.outputs.target }}
rpc_url: 'http://127.0.0.1:${{ env.ANVIL_PORT }}'
+ protocol: ${{ github.event.inputs.protocol || vars.ATUPA_PROTOCOL || '' }}
+ vm: ${{ github.event.inputs.vm || '' }}
config: 'atupa.toml'
post_comment: 'true'
upload_svg: 'true'
+ upload_json: 'true'
- name: Stop Anvil
if: always()
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 825aa8c..cdf23d1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,8 +2,9 @@
# Atupa Release — Automated Binary Compilation & Publishing
#
# Triggered when a new version tag (v*) is pushed.
-# Performs a cross-platform matrix build (Linux, macOS, Windows),
-# bundles the Studio frontend into the binary, and creates a GitHub Release.
+# Performs a cross-platform matrix build (Linux, macOS, Windows),
+# bundles the Studio frontend into the binary, generates SHA-256 checksums,
+# and creates a GitHub Release before publishing crates in order.
# ─────────────────────────────────────────────────────────────────────────────
name: 🚀 Release
@@ -107,6 +108,7 @@ jobs:
cd target/${{ matrix.target }}/release
tar czf ../../../${{ matrix.asset_name }} atupa
cd ../../../
+ sha256sum ${{ matrix.asset_name }} > ${{ matrix.asset_name }}.sha256
- name: Package Binary (Windows)
if: runner.os == 'Windows'
@@ -115,12 +117,16 @@ jobs:
cd target/${{ matrix.target }}/release
Compress-Archive -Path atupa.exe -DestinationPath ../../../${{ matrix.asset_name }}
cd ../../../
+ $hash = (Get-FileHash -Path ${{ matrix.asset_name }} -Algorithm SHA256).Hash.ToLower()
+ "$hash ${{ matrix.asset_name }}" | Out-File -FilePath "${{ matrix.asset_name }}.sha256" -Encoding ascii
# 🚀 Upload to the release created in Phase 1
- name: Upload Asset to Release
uses: softprops/action-gh-release@v2
with:
- files: ${{ matrix.asset_name }}
+ files: |
+ ${{ matrix.asset_name }}
+ ${{ matrix.asset_name }}.sha256
tag_name: ${{ github.ref_name }}
# ── Phase 3: Publish to Crates.io ──────────────────────────────────────────
@@ -159,4 +165,5 @@ jobs:
echo "⚠️ CRATES_IO_TOKEN not found. Skipping publish step."
exit 0
fi
+ chmod +x ./publish.sh
./publish.sh
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 97516d7..7851973 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -1,4 +1,4 @@
-name: Rust
+name: Rust CI
on:
push:
@@ -11,7 +11,7 @@ env:
jobs:
fmt:
- name: Format Check
+ name: 🎨 Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -19,39 +19,45 @@ jobs:
with:
components: rustfmt
- uses: Swatinem/rust-cache@v2
- - name: Run fmt
+ - name: Check code formatting
run: cargo fmt --all -- --check
clippy:
- name: Lint (Clippy)
+ name: 🔍 Lint (Clippy)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
+ - uses: actions/setup-node@v4
with:
- components: clippy
- - uses: Swatinem/rust-cache@v2
- - name: Build Studio
+ node-version: 22
+ - name: Build Studio Frontend
run: |
cd studio
- npm install
+ npm ci || npm install
npm run build
+ - uses: Swatinem/rust-cache@v2
- name: Run clippy
- run: cargo clippy --workspace --all-targets -- -D warnings
+ run: cargo clippy --workspace --all-targets
test:
- name: Test Suite
+ name: 🧪 Workspace Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- - uses: Swatinem/rust-cache@v2
- - name: Build Studio
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: 'npm'
+ cache-dependency-path: 'studio/package-lock.json'
+ - name: Build Studio Frontend
run: |
cd studio
- npm install
+ npm ci || npm install
npm run build
- - name: Cargo Check
+ - uses: Swatinem/rust-cache@v2
+ - name: Workspace Check
run: cargo check --workspace
- - name: Run tests
+ - name: Run all workspace tests
run: cargo test --workspace
diff --git a/.gitignore b/.gitignore
index 4186ae9..d89f060 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,3 +59,6 @@ artifacts/
# Playground
playground/out/
playground/cache/
+
+
+demo
\ No newline at end of file
diff --git a/ARBITRUM_UNIFIED_PROPOSAL.md b/ARBITRUM_UNIFIED_PROPOSAL.md
deleted file mode 100644
index 2905568..0000000
--- a/ARBITRUM_UNIFIED_PROPOSAL.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Aave/Arbitrum Unified: The High-Fidelity Execution Layer
-
-**Project Name**: Atupa Unified Execution Suite
-**Grant Requested**: $50,000
-**Category**: Infrastructure & Developer Tooling
-**Target Ecosystem**: Arbitrum (One, Nova, Orbit)
-
----
-
-## 🏮 The Vision: Bridging the "WASM-EVM Gap"
-The Arbitrum ecosystem is at a historical turning point with the launch of **Stylus**. While Stylus allows for massive compute efficiency in Rust/WASM, it introduces a "Visibility Black Hole" for the millions of existing Solidity developers.
-
-**Atupa** is the first unified execution profiler that bridges this gap. Instead of forcing developers to use fragmented tools for different parts of a transaction, Atupa provides a contiguous, high-fidelity view of the entire **Hybrid Execution Path**.
-
-## 🏗 Why Atupa vs. Niche Profilers?
-Existing tools focus strictly on the WASM Native VM. Atupa focuses on the **Transaction Lifecycle**.
-
-- **Hybrid Interoperability**: Atupa correlates standard EVM traces with Stylus HostIO traces. For the first time, a developer can see exactly how a Solidity call-stack transitions into a WASM logic-gate and back.
-- **Unified Metrics**: We normalize standard Gas and Stylus Ink into a single, comprehensive "Cost-of-Execution" report.
-- **Protocol-Awareness**: With built-in adapters for **Aave**, **GHO**, and **Lido**, Atupa doesn't just show opcodes—it shows protocol health.
-
-## 🛠 Milestones (4 Months)
-
-### Milestone 1: The Nitro Unified Tracer ($15,000) - [✅ ALPHA DELIVERED]
-- Develop the `atupa-nitro` adapter for stitching EVM and WASM traces.
-- Implement the "Hybrid Flamegraph" engine.
-- Deliverable: Alpha CLI tool (`atupa`) available in `bin/atupa`.
-
-### Milestone 2: Institutional Protocol Adapters ($20,000) - [✅ IN PROGRESS]
-- Integrate deep decoding for Aave v3, GHO, and Lido on Arbitrum.
-- Functional Adapters: `atupa-aave` and `atupa-lido` are now operational in the workspace.
-- Implement "Liquidation-Efficiency" and "Oracle-Latency" tracking.
-- Deliverable: Advanced profiling reports accessible via `atupa audit`.
-
-### Milestone 3: The Atupa Studio & CI Suite ($15,000)
-- Launch the high-performance Web Viewer for interactive trace exploration.
-- Implement GitHub Actions for "Hybrid Gas Regression" tracking.
-- Deliverable: Full production suite and public documentation.
-
----
-
-## 👥 The Team: One Block
-We are experts in high-performance Rust networking and Ethereum infrastructure.
-- **Lead Developer**: Michael Dean Oyewole (`@dean8ix`)
-- **Core Library**: [atupa-core (v0.1.0)](https://crates.io/crates/atupa-core)
-
----
-🏮 *One Block: The Transparency Layer for Ethereum.*
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 0f0e947..d43d622 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -1,32 +1,104 @@
-# Atupa Suit: System Architecture
+# 🏮 Atupa System Architecture
-The Atupa Suite is designed as a modular, high-performance infrastructure stack that provides transparency for the "Multi-VM" future of Ethereum. It separates the heavy lifting of raw trace parsing from the high-level business logic of protocol-specific auditing.
+Atupa is a high-performance, modular infrastructure stack designed as a **Universal Multi-VM Execution Profiler**. This document details the technical design, data normalization strategies, and crate-level relationships that power the suite across diverse execution environments.
+
+---
+
+## 🏛 Core Philosophy: The Unified Trace Model
+
+The central challenge Atupa solves is the fragmentation of execution data across different Virtual Machines (EVM, WASM, Cairo, SVM, Soroban). Each VM has its own native "gas" units, log structures, and call-stack representations.
+
+Atupa addresses this by normalizing all execution data into a **Unified Trace Step** (`TraceStep`):
+
+```rust
+pub struct TraceStep {
+ pub pc: u64, // Program counter or instruction index
+ pub op: String, // Opcode, HostFn name, or Program Label
+ pub gas: u64, // Gas remaining
+ pub gas_cost: u64, // Normalized execution weight
+ pub depth: u16, // Call-stack depth
+ pub vm_kind: VmKind, // The source VM (Evm, Stylus, Solana, Starknet, Stellar)
+ pub stack: Option
- High-Fidelity Ethereum Tracing, Profiling & Visual Analysis Suite + Universal Multi-VM Execution Profiler & Visual Analysis Suite
@@ -17,18 +17,18 @@
---
-**Atupa** (meaning *Lantern/Lamp*) is a professional-grade EVM + Arbitrum Stylus execution profiler. It turns raw JSON-RPC `debug_traceTransaction` and `stylusTracer` logs into actionable visual insights — from gas flamegraphs to unified EVM/WASM execution dashboards.
+**Atupa** is a professional-grade **Universal Multi-VM Execution Profiler**. It provides a unified observability and regression analysis layer across diverse blockchain Virtual Machines — including **EVM**, **Arbitrum Stylus (WASM)**, **Starknet (Cairo)**, **Solana (SVM)**, and **Stellar (Soroban)** — turning raw execution traces into actionable visual insights and CI-ready differential reports.
## ✨ Key Features
-- **🔥 Unified EVM + Stylus Tracing**: Stitches data from both the EVM and Stylus WASM runtime into a single coherent execution timeline.
-- **🏮 Atupa Studio**: A local-first web visualizer — drop a `report.json` to instantly render metric cards, HostIO hot paths, and a step-by-step trace inspector.
-- **📊 HostIO Flamegraph**: Surfaces the most expensive Stylus Host I/O calls (`storage_flush_cache`, `native_keccak256`, etc.) ranked by gas-equivalent cost.
-- **🚨 Crisp Revert Identification**: Instantly identifies failing sub-calls with high-contrast highlights.
-- **🔍 Smart Contract Resolution**: Automatically resolves hex addresses to verified contract names via Etherscan V2.
-- **🚀 Automated CI/CD Pipeline**: Built-in `atupa init` for zero-config gas regression gating in GitHub Actions.
-- **💉 Protocol-Specific Deep Auditing**: Built-in deep traces for **Lido stETH** and **Aave v3**.
-- **🛠 Modular Library Architecture**: Pure Rust workspace with specialized crates for adapters, RPC, parsing, and output.
+- **🌐 Universal Multi-VM Profiling**: Unified tracing for EVM, Arbitrum Stylus (WASM), Starknet (Cairo), Solana (SVM), and Stellar (Soroban).
+- **🔥 Dual-VM Stitching**: Seamlessly reconstructs execution timelines across VM boundaries (e.g. EVM calling Stylus WASM with HostIO breakdown).
+- **📊 Protocol-Aware Gas Analysis**: Specialized cost mapping for non-EVM units, including Solana Compute Units (CU), Soroban HostFn weights, and Cairo execution steps.
+- **🏮 Atupa Studio**: An embedded local-first web visualizer (`atupa studio`) — drop a `report.json` to instantly render cross-chain metric cards and interactive flamegraphs.
+- **🔍 Smart Contract Resolution**: Automatically resolves addresses to verified contract names via Etherscan, Starkscan, and explorer resolvers.
+- **🚀 Automated CI/CD Regression Gates**: Built-in zero-config gas regression gating for GitHub Actions with sticky PR commenting and SVG artifact generation.
+- **🔬 Protocol-Specific Deep Auditing**: Built-in deep tracers for **Aave v3 / GHO** and **Lido stETH**.
+- **🛠 Modular Architecture**: 13 pure Rust crates with zero-cost abstractions, strict type safety, and clean separation of concerns.
## 🚀 Quick Start
@@ -40,10 +40,10 @@ cargo install atupa
### 🏮 One-Click Initialization
-Bootstrap your project with Atupa profiling and automated CI regression in one command.
+Bootstrap your project with Atupa profiling and automated CI regression in one command:
```bash
-# Detects Foundry/Hardhat and sets up atupa.toml + GitHub Action
+# Detects Foundry, Hardhat, or Stylus and generates atupa.toml + GitHub Action + profile script
atupa init
```
@@ -53,68 +53,113 @@ atupa init
# Capture an Arbitrum Stylus transaction (summary to terminal)
atupa capture --tx 0x... --rpc https://arb-mainnet.g.alchemy.com/v2/KEY
-# Export as JSON for Atupa Studio
-atupa capture --tx 0x... --rpc https://... --output json --file report.json
+# Capture a Solana transaction (SVM Compute Unit breakdown)
+atupa capture --tx 5Z9... --rpc https://api.mainnet-beta.solana.com
-# Deep protocol audit (Lido or Aave)
-atupa audit --protocol lido --tx 0x...
+# Capture a Starknet transaction (Cairo execution steps)
+atupa capture --tx 0x... --rpc https://starknet-mainnet.public.blastapi.io
-# Compare execution cost of two transactions
-atupa diff --base 0x... --target 0x...
-```
+# Capture a Stellar transaction (Soroban diagnostic events)
+atupa capture --tx 0x... --rpc https://soroban-testnet.stellar.org
+
+# Explicitly specify VM runtime if ambiguous
+atupa capture --tx 0x... --vm stylus --rpc https://arb-sepolia.g.alchemy.com/v2/KEY
-## 🛡 Automated Gas Regression (CI)
+# Export report as JSON and generate an SVG flamegraph simultaneously
+atupa capture --tx 0x... --output json --file report.json --profile
+```
-Atupa is designed to sit inside your CI/CD pipeline. Use `atupa init` to generate a `.github/workflows/atupa.yml` file that:
-1. Runs your profile scripts on the base branch (baseline).
-2. Runs your profile scripts on the pull request branch (target).
-3. Compares results and fails the CI if gas regressions exceed your `atupa.toml` thresholds.
+### Comparing Transactions (Differential Profiling)
```bash
-atupa diff --base 0xBASE_TX --target 0xPR_TX --protocol lido
+# Compare execution costs of two transactions
+atupa diff --base 0xBASE_TX --target 0xTARGET_TX --rpc https://...
+
+# Enforce a CI regression threshold (fail if gas increases by > 2%)
+atupa diff --base 0xBASE_TX --target 0xTARGET_TX --threshold 2.0 --markdown --svg
+
+# Run protocol deep diff (Aave v3 or Lido stETH)
+atupa diff --base 0xBASE_TX --target 0xTARGET_TX --protocol aave
```
-### Run the Demo
+### Generating an Interactive SVG Flamegraph
+
```bash
-atupa profile --demo
+# Offline demo trace (no RPC required)
+atupa profile --demo --out profile_demo.svg
+
+# Profile a live on-chain transaction
+atupa profile --tx 0x... --rpc https://arb-mainnet.g.alchemy.com/v2/KEY
```
+---
+
## 🏮 Atupa Studio
-Atupa Studio is a local-first web visualizer for your execution traces.
+Atupa Studio is an embedded local web visualizer for interactive trace inspection and flamegraph exploration.
```bash
-# Start the Studio dev server
-cd studio && npm install && npm run dev
+# Launch Studio and auto-open in browser
+atupa studio
+
+# Or capture a trace and launch Studio with the report automatically loaded
+atupa capture --tx 0x... --rpc https://... --studio
```
-Then open **http://localhost:5173**, generate a trace with `--output json --file report.json`, and drop the file into the Studio. The dashboard instantly renders:
+---
-- **Execution Metrics** — EVM Gas, Stylus Ink, HostIO call counts, VM boundary crossings
-- **HostIO Hot Paths** — Ranked table with inline distribution bars
-- **Trace Inspector** — Paginated, filterable, searchable step-by-step execution viewer
+## 🛡 Automated Gas Regression (GitHub Action)
+
+Integrate Atupa into your repository's CI pipeline with [`One-Block-Org/Atupa`](action.yml):
+
+```yaml
+- name: Run Atupa Gas Regression Check
+ uses: One-Block-Org/Atupa@main
+ with:
+ base_tx: ${{ steps.baseline.outputs.tx_hash }}
+ target_tx: ${{ steps.target.outputs.tx_hash }}
+ rpc_url: ${{ secrets.ATUPA_RPC_URL }}
+ config: 'atupa.toml'
+ post_comment: 'true'
+ upload_svg: 'true'
+ upload_json: 'true'
+```
+
+---
-## 📦 Project Structure
+## 📦 Monorepo Architecture
-Atupa is built as a highly modular monorepo:
+The workspace is organized into modular crates:
| Crate / Directory | Description |
|---|---|
-| [`bin/atupa`](bin/atupa) | The primary command-line interface. |
-| [`studio/`](studio) | Atupa Studio — Vite + React web visualizer. |
-| [`crates/atupa-sdk`](crates/atupa-sdk) | Public-facing SDK for programmatic tracing. |
-| [`crates/atupa-core`](crates/atupa-core) | Shared types and core configuration logic. |
-| [`crates/atupa-parser`](crates/atupa-parser) | Aggregation engine that collapses EVM traces. |
-| [`crates/atupa-nitro`](crates/atupa-nitro) | Arbitrum Nitro dual-VM stitcher (EVM + Stylus). |
-| [`crates/atupa-rpc`](crates/atupa-rpc) | Async Ethereum JSON-RPC client & Etherscan resolver. |
-| [`crates/atupa-lido`](crates/atupa-lido) | Specialized adapter for Lido stETH. |
-| [`crates/atupa-aave`](crates/atupa-aave) | Specialized adapter for Aave v3 & GHO. |
+| [`bin/atupa`](bin/atupa) | The primary command-line interface (`profile`, `capture`, `audit`, `diff`, `studio`, `init`). |
+| [`studio/`](studio) | Atupa Studio — Vite + React 19 + TypeScript web visualizer. |
+| [`crates/atupa-sdk`](crates/atupa-sdk) | High-level SDK facade for programmatic profiling and multi-VM routing. |
+| [`crates/atupa-core`](crates/atupa-core) | Core data models, `TraceStep`, `GasCategory`, `VmKind`, and configuration types. |
+| [`crates/atupa-adapters`](crates/atupa-adapters) | Common adapter registry and standard protocol traits (Uniswap v4, ERC-20). |
+| [`crates/atupa-parser`](crates/atupa-parser) | Selector decoders, trace normalizers, and stack aggregators. |
+| [`crates/atupa-output`](crates/atupa-output) | Standalone SVG flamegraph and visual diff flamegraph rendering engines. |
+| [`crates/atupa-nitro`](crates/atupa-nitro) | Arbitrum Nitro dual-VM clock stitcher (EVM + Stylus WASM HostIOs). |
+| [`crates/atupa-starknet`](crates/atupa-starknet) | Starknet Cairo execution flattener and builtin weight calculators. |
+| [`crates/atupa-solana`](crates/atupa-solana) | Solana Sealevel VM (SVM) log-stitching state machine. |
+| [`crates/atupa-stellar`](crates/atupa-stellar) | Stellar Soroban diagnostic event tracer and HostFn cost estimator. |
+| [`crates/atupa-rpc`](crates/atupa-rpc) | Async multi-chain RPC client, raw trace types, and Etherscan resolver. |
+| [`crates/atupa-aave`](crates/atupa-aave) | Specialized deep tracer for Aave v3 and GHO stablecoin operations. |
+| [`crates/atupa-lido`](crates/atupa-lido) | Specialized deep tracer for Lido stETH staking operations. |
+
+---
## 🤝 Contributing
-We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more details.
+We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) and [Testing Guide](TESTING_GUIDE.md) for details.
+
+## 📖 Further Reading
+
+- [**The Atupa Vision**](docs/VISION.md) — Why universal multi-VM execution observability matters.
+- [**System Architecture**](ARCHITECTURE.md) — Deep dive into normalization, dual-VM clock stitching, and pipeline design.
+- [**Adapter Guide**](docs/ADAPTER_GUIDE.md) — Step-by-step guide to adding support for new Virtual Machines.
## 📄 License
Atupa is dual-licensed under the [MIT License](LICENSE-MIT) and the [Apache License, Version 2.0](LICENSE-APACHE).
-You may use this software under either license, at your option.
diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md
index 1482933..1f377be 100644
--- a/TESTING_GUIDE.md
+++ b/TESTING_GUIDE.md
@@ -1,107 +1,107 @@
-# Atupa Testing Guide
+# 🧪 Atupa Testing Guide
-This document provides a comprehensive, step-by-step framework for testing the entirety of the Atupa project. It covers automated workspace tests, local CLI verification, and end-to-end integration flows.
+This document provides a comprehensive framework for testing the entire Atupa monorepo, covering automated Rust workspace tests, frontend Studio testing, local CLI verification, and CI regression checks.
---
-## 1. Prerequisites & Environment Setup
+## 1. Automated Workspace Testing (Rust & Frontend)
-Most advanced features of Atupa (Trace Capturing, Protocol Audits, Differential Profiling) require live Ethereum/Arbitrum node data. Ensure your environment is configured before starting E2E testing.
+### Rust Test Suite
+The project leverages Cargo's test runner across all 13 crates:
-### Environment Variables
-For live network tests, expose the following environment variables:
```bash
-export ATUPA_RPC_URL="https://arb-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY"
-export ETHERSCAN_API_KEY="YOUR_ARBISCAN_KEY"
-```
-*(If you do not have an API key, you can still verify functionality using the `--demo` mode or cargo unit testing).*
+# 1. Format Check
+cargo fmt --all -- --check
----
+# 2. Strict Linting
+cargo clippy --workspace --all-targets -- -D warnings
-## 2. Automated Workspace Testing (CI/CD Quality)
+# 3. All Unit & Integration Tests
+cargo test --workspace
+```
-The project leverages Cargo's highly parallel test runner to ensure unit and integration validity across all workspace crates (`atupa-core`, `atupa-parser`, `atupa-lido`, `atupa-aave`, etc.).
+### Studio Frontend Testing & Linting
+Verify the embedded React 19 visualizer:
-**Run the fundamental verification suite:**
```bash
-# 1. Formatting
-cargo fmt --all -- --check
+cd studio
-# 2. Linting
-cargo clippy --workspace --all-targets --all-features -- -D warnings
+# 1. Lint TypeScript and React Components
+npm run lint
-# 3. All Unit & Integration Tests
-cargo test --workspace --all-features
+# 2. Build Static Bundle for Binary Embedding
+npm run build
```
-> **Success Criteria:** Zero warnings and passing results for all doc tests, unit tests, and integration assertions.
---
-## 3. CLI Feature Testing (Local Verification)
+## 2. CLI End-to-End Verification
-To test the CLI application manually and visually, compile it locally and execute its main subcommands. E2E testing evaluates the terminal presentation, progress bar animations, rendering, and API communication.
+Compile the CLI in release mode:
-Compile first to ensure performance:
```bash
cargo build --release -p atupa
alias atupa="./target/release/atupa"
```
-### Flow 1: Offline / Demo Verification
-Verify the CLI profiling outputs and visual styles without relying on the network.
-```bash
-# Run a demo profile
-atupa profile --demo --tx 0x0
-```
-> **Expected Output:** You should see a visually distinct `eprintln!` profiling banner and a well-formatted statistical JSON structure showing unified traces.
+### Flow 1: Offline / Demo Flamegraph
+Verify SVG generation without network dependency:
-### Flow 2: Live RPC Connection / Trace Capture
-Test the `capture` command, evaluating both `stdout` output generation and diagnostic `stderr` headers. Replace `0x...` with a real target Arbitrum/Ethereum transaction.
```bash
-atupa capture --tx 0x8a923...
+atupa profile --demo --out profile_demo.svg
```
-> **Expected Output:** A cleanly rendered summary payload. (Verify that `atupa capture --tx ... > report.txt` results in the report being written cleanly to file without UI spinners overriding `stdout`).
+> **Expected Output:** Profile banner, terminal confirmation, and a valid `profile_demo.svg` file created.
-### Flow 3: Specific Protocol Audits
-Test the semantic decoding layers. These verify that the abstract trace data successfully parses into protocol-specific models.
+### Flow 2: Multi-VM Live Trace Captures
+Test trace capture across supported VM runtimes:
-**A. Test Aave v3 Decoding**
```bash
-atupa audit --protocol aave --tx 0x93ab...
-```
-> **Expected Output:** A generated diagnostic table identifying the Flash Loan execution, debt updates, user reserve states, and liquidation values.
+# Arbitrum Nitro / EVM
+atupa capture --tx 0x8a923... --rpc https://arb-mainnet.g.alchemy.com/v2/KEY
-**B. Test Lido stETH Execution**
-```bash
-atupa audit --protocol lido --tx 0x1fca...
+# Solana Sealevel VM (SVM)
+atupa capture --tx 5Z9... --rpc https://api.mainnet-beta.solana.com
+
+# Starknet Cairo VM
+atupa capture --tx 0x... --rpc https://starknet-mainnet.public.blastapi.io
+
+# Stellar Soroban WASM VM
+atupa capture --tx 0x... --rpc https://soroban-testnet.stellar.org
```
-> **Expected Output:** Verification of the staking pipeline (Deposit Event → Oracle Refresh → Staking Limits → Treasury Accounting).
-### Flow 4: Differential Profiling (Cost & Execution Delta)
-Verify the `--diff` execution flow, calculating the divergence in bytecode pathways or gas consumption between two similar transactions.
+### Flow 3: Protocol Deep Auditing
+Verify specialized semantic decoding for DeFi protocols:
```bash
-atupa diff 0xBASE_TX_HASH 0xTARGET_TX_HASH
+# Aave v3 + GHO Stablecoin Audit
+atupa audit --protocol aave --tx 0x...
+
+# Lido stETH Audit
+atupa audit --protocol lido --tx 0x...
```
-> **Expected Output:** A dual-table view or structured report listing `-X%` or `+Y%` deltas for Execution Steps, Memory Allocation, EVM Gas, and System VM parameters.
----
+### Flow 4: Differential Profiling & CI Regression
+Verify differential execution analysis:
+
+```bash
+# Basic comparison
+atupa diff --base 0xBASE_HASH --target 0xTARGET_HASH --rpc https://...
-## 4. Specific Crate Example Evaluation
+# Enforce regression threshold with Markdown report & SVG generation
+atupa diff --base 0xBASE_HASH --target 0xTARGET_HASH --threshold 2.0 --markdown --svg
+```
-To test individual programmatic SDK behaviors independent of the main CLI app, Atupa includes runnable examples inside specific crates.
+### Flow 5: Local Studio Server
+Launch the embedded web server and verify browser loading:
-### Run the Trace Parser Example
-This runs a simulated test of the underlying log unification parser:
```bash
-RUST_LOG=info cargo run -p atupa-parser --example trace_analysis
+atupa studio --port 5173
```
-> **Expected Output:** The parsed log analysis will stream into the standard console output using `env_logger`. Ensure the output renders properly without utilizing `println!`.
+> **Expected Output:** Local server starts on `http://localhost:5173` and automatically opens the visualizer in the default browser.
---
-## 5. Security & Safety
+## 3. Security & Code Safety Rules
-Atupa follows strict safety constraints. As part of your testing loop:
-- Ensure the **"No-`println!`" rule** is respected. The CLI `stdout` must remain untainted for data payload piping, utilizing `eprintln!` strictly for diagnostics.
-- Ensure all dependencies remain synchronized avoiding version drift across workspaces. Run `cargo update` locally and re-run step 2 (Automated Workspace Testing) to check against dependency vulnerabilities.
+- **Zero Clippy Warnings**: All commits must pass `cargo clippy --workspace --all-targets -- -D warnings`.
+- **Clean stdout**: CLI diagnostic messages use `eprintln!`; structured JSON or metric payloads use `stdout` so output can be safely piped into files or downstream tools.
diff --git a/action.yml b/action.yml
index 59c5b6d..e8e6761 100644
--- a/action.yml
+++ b/action.yml
@@ -1,9 +1,9 @@
name: 'Atupa Gas Regression Check'
description: |
- Automated gas regression detection for Ethereum & Arbitrum Stylus transactions.
- Runs atupa diff between two transactions, uploads an SVG flamegraph artifact,
+ Automated multi-VM gas regression detection for EVM, Arbitrum Stylus, Solana, Starknet, and Stellar.
+ Runs `atupa diff` between two transactions, uploads SVG flamegraph and JSON artifacts,
and posts a sticky Markdown report to the Pull Request.
-author: 'One Block Ltd'
+author: 'One Block'
branding:
icon: 'zap'
color: 'orange'
@@ -12,10 +12,10 @@ branding:
inputs:
base_tx:
- description: 'Baseline transaction hash (e.g. last commit on main)'
+ description: 'Baseline transaction hash or signature (e.g. last commit on main)'
required: true
target_tx:
- description: 'Target transaction hash (e.g. current PR branch)'
+ description: 'Target transaction hash or signature (e.g. current PR branch)'
required: true
rpc_url:
description: 'RPC endpoint for the target network'
@@ -25,6 +25,10 @@ inputs:
description: 'Optional: protocol-specific deep diff (aave | lido)'
required: false
default: ''
+ vm:
+ description: 'Explicit VM target runtime (evm | stylus | starknet | solana | stellar)'
+ required: false
+ default: ''
threshold:
description: 'Simple gas increase threshold % (overrides atupa.toml)'
required: false
@@ -65,17 +69,17 @@ runs:
using: 'composite'
steps:
- # 1. Source-aware binary setup (stolen & improved from Stylus-Trace)
- # - If we are INSIDE the Atupa repo, build from source and cache.
- # - If we are in a CONSUMER repo, install from crates.io.
+ # 1. Source-aware binary setup
+ # - If inside the Atupa repository, build from source and cache.
+ # - If in a consumer repository, install from crates.io.
- name: Setup Atupa Binary
shell: bash
run: |
if [ -f "${{ github.workspace }}/bin/atupa/Cargo.toml" ]; then
- echo "🏠 Running inside Atupa repo — building from source…"
+ echo "🏠 Running inside Atupa repository — building from source…"
if [ -d "${{ github.workspace }}/studio" ]; then
echo "🎨 Building Atupa Studio frontend…"
- cd studio && npm install && npm run build && cd ..
+ cd "${{ github.workspace }}/studio" && npm install && npm run build && cd "${{ github.workspace }}"
fi
cargo build --release -p atupa
echo "${{ github.workspace }}/target/release" >> $GITHUB_PATH
@@ -96,6 +100,11 @@ runs:
PROTO_FLAG="--protocol ${{ inputs.protocol }}"
fi
+ VM_FLAG=""
+ if [ -n "${{ inputs.vm }}" ]; then
+ VM_FLAG="--vm ${{ inputs.vm }}"
+ fi
+
THRESHOLD_FLAG=""
if [ -n "${{ inputs.threshold }}" ]; then
THRESHOLD_FLAG="--threshold ${{ inputs.threshold }}"
@@ -119,21 +128,22 @@ runs:
--markdown \
--svg \
$PROTO_FLAG \
+ $VM_FLAG \
$THRESHOLD_FLAG \
$CONFIG_FLAG \
$JSON_FLAG && REGRESSION=false || REGRESSION=true
# Discover output paths
- BASE_SHORT="${{ inputs.base_tx }}"
- TARGET_SHORT="${{ inputs.target_tx }}"
- MD_PATH="artifacts/diff/${BASE_SHORT:0:10}_vs_${TARGET_SHORT:0:10}.md"
- SVG_PATH="artifacts/diff/${BASE_SHORT:0:10}_vs_${TARGET_SHORT:0:10}.svg"
+ BASE_CLEAN="${{ inputs.base_tx }}"
+ TARGET_CLEAN="${{ inputs.target_tx }}"
+ MD_PATH="artifacts/diff/${BASE_CLEAN:0:10}_vs_${TARGET_CLEAN:0:10}.md"
+ SVG_PATH="artifacts/diff/${BASE_CLEAN:0:10}_vs_${TARGET_CLEAN:0:10}.svg"
echo "md_path=${MD_PATH}" >> $GITHUB_OUTPUT
echo "svg_path=${SVG_PATH}" >> $GITHUB_OUTPUT
echo "regression_detected=${REGRESSION}" >> $GITHUB_OUTPUT
- # 3. Upload SVG flamegraph as a workflow artifact (link appears in PR checks tab)
+ # 3. Upload SVG flamegraph as a workflow artifact
- name: Upload SVG Flamegraph
if: inputs.upload_svg == 'true' && always()
uses: actions/upload-artifact@v4
@@ -143,17 +153,17 @@ runs:
if-no-files-found: warn
retention-days: 30
- # 3.5 Upload JSON Reports for Atupa Studio
- - name: Upload JSON Reports
+ # 3.5 Upload JSON and Diff Artifacts for Atupa Studio
+ - name: Upload Trace & Diff Artifacts
if: inputs.upload_json == 'true' && always()
uses: actions/upload-artifact@v4
with:
- name: atupa-reports-${{ inputs.base_tx != '' && inputs.base_tx || 'base' }}
- path: artifacts/reports/
+ name: atupa-artifacts-${{ inputs.base_tx != '' && inputs.base_tx || 'base' }}
+ path: artifacts/
if-no-files-found: warn
retention-days: 30
- # 4. Post sticky PR comment (updates in-place on every new push to PR)
+ # 4. Post sticky PR comment (updates in-place on every push to PR)
- name: Post Sticky PR Comment
if: inputs.post_comment == 'true' && github.event_name == 'pull_request' && always()
continue-on-error: true
@@ -162,12 +172,12 @@ runs:
header: atupa-gas-diff
path: ${{ steps.diff.outputs.md_path }}
- # 5. Finally: fail the job if a regression was detected
+ # 5. Enforce regression gate
- name: Enforce Regression Gate
shell: bash
run: |
if [ "${{ steps.diff.outputs.regression_detected }}" = "true" ]; then
- echo "❌ Atupa detected a gas regression. See the PR comment for details."
+ echo "❌ Atupa detected a gas regression exceeding configured limits. See the PR comment for details."
exit 1
fi
echo "✅ No gas regressions detected."
diff --git a/artifacts/capture/test.json b/artifacts/capture/test.json
deleted file mode 100644
index 94def93..0000000
--- a/artifacts/capture/test.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
- "version": "1.0.0",
- "transaction_hash": "0xfd1fe741d0e7b2e5597880e77ca5c41936de7c54f07da0fe1caffddfbeb352d3",
- "total_gas": 442732195,
- "hostio_summary": {
- "total_calls": 17,
- "by_type": {
- "storage_flush_cache": 1,
- "storage_load": 3,
- "read_args": 1,
- "native_keccak256": 3,
- "storage_cache": 3,
- "other": 3,
- "msg_reentrant": 1,
- "write_result": 1,
- "msg_value": 1
- },
- "total_hostio_gas": 442732195
- },
- "hot_paths": [
- {
- "stack": "storage_flush_cache",
- "gas": 400068073,
- "percentage": 90.36344713986747,
- "category": "StorageExpensive",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "storage_load_bytes32",
- "gas": 42155440,
- "percentage": 9.521656765892077,
- "category": "StorageNormal",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "native_keccak256",
- "gas": 365400,
- "percentage": 0.08253296329624278,
- "category": "Crypto",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "storage_cache_bytes32",
- "gas": 55440,
- "percentage": 0.012522242707016146,
- "category": "StorageNormal",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "write_result",
- "gas": 41162,
- "percentage": 0.009297268295566354,
- "category": "Memory",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "read_args",
- "gas": 16440,
- "percentage": 0.003713305737794831,
- "category": "Memory",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "msg_value",
- "gas": 13440,
- "percentage": 0.003035695201700884,
- "category": "System",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "msg_reentrant",
- "gas": 8400,
- "percentage": 0.0018973095010630524,
- "category": "System",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "pay_for_memory_grow",
- "gas": 8400,
- "percentage": 0.0018973095010630524,
- "category": "Memory",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "user_entrypoint",
- "gas": 0,
- "percentage": 0.0,
- "category": "UserCode",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- },
- {
- "stack": "user_returned",
- "gas": 0,
- "percentage": 0.0,
- "category": "UserCode",
- "source_hint": {
- "file": "unknown",
- "line": null,
- "column": null,
- "function": "0x0"
- }
- }
- ],
- "all_stacks": [
- {
- "stack": "storage_flush_cache",
- "weight": 400068073,
- "last_pc": 0
- },
- {
- "stack": "storage_load_bytes32",
- "weight": 42155440,
- "last_pc": 0
- },
- {
- "stack": "native_keccak256",
- "weight": 365400,
- "last_pc": 0
- },
- {
- "stack": "storage_cache_bytes32",
- "weight": 55440,
- "last_pc": 0
- },
- {
- "stack": "write_result",
- "weight": 41162,
- "last_pc": 0
- },
- {
- "stack": "read_args",
- "weight": 16440,
- "last_pc": 0
- },
- {
- "stack": "msg_value",
- "weight": 13440,
- "last_pc": 0
- },
- {
- "stack": "msg_reentrant",
- "weight": 8400,
- "last_pc": 0
- },
- {
- "stack": "pay_for_memory_grow",
- "weight": 8400,
- "last_pc": 0
- },
- {
- "stack": "user_entrypoint",
- "weight": 0,
- "last_pc": 0
- },
- {
- "stack": "user_returned",
- "weight": 0,
- "last_pc": 0
- }
- ],
- "generated_at": "2026-04-15T16:15:00.860452520+00:00"
-}
\ No newline at end of file
diff --git a/artifacts/capture/test.svg b/artifacts/capture/test.svg
deleted file mode 100644
index 5bd0352..0000000
--- a/artifacts/capture/test.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/bin/atupa/Cargo.toml b/bin/atupa/Cargo.toml
index 0a7da50..c1a4eee 100644
--- a/bin/atupa/Cargo.toml
+++ b/bin/atupa/Cargo.toml
@@ -2,9 +2,8 @@
name = "atupa"
version = { workspace = true }
edition = { workspace = true }
-authors = { workspace = true }
license = { workspace = true }
-description = "atupa — Unified EVM + Stylus Execution Profiler CLI"
+description = "Atupa — Universal Multi-VM Execution Profiler"
readme = { workspace = true }
# Standalone binary target
@@ -22,6 +21,9 @@ atupa-aave = { workspace = true }
atupa-lido = { workspace = true }
atupa-parser = { workspace = true }
atupa-output = { workspace = true }
+atupa-starknet= { workspace = true }
+atupa-solana = { workspace = true }
+atupa-stellar = { workspace = true }
# CLI
clap = { workspace = true }
diff --git a/bin/atupa/dist/assets/index-BA5VvGth.js b/bin/atupa/dist/assets/index-BA5VvGth.js
new file mode 100644
index 0000000..5e08d39
--- /dev/null
+++ b/bin/atupa/dist/assets/index-BA5VvGth.js
@@ -0,0 +1,9 @@
+var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function S(){}var C={H:null,A:null,T:null,S:null},te=Object.prototype.hasOwnProperty;function ne(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function re(e,t){return ne(e.type,t,e.props)}function ie(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ae(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var w=/\/+/g;function oe(e,t){return typeof e==`object`&&e&&e.key!=null?ae(``+e.key):t.toString(36)}function se(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(S,S):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ce(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ce(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+oe(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(w,`$&/`)+`/`),ce(o,r,i,``,function(e){return e})):o!=null&&(ie(o)&&(o=re(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(w,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Kt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Kt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Kt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Kt(n.imageSizes)+`"]`)):i+=`[href="`+Kt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Kt(r)+`"][href="`+Kt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),Dt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=Et(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);Dt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=ge.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=Et(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=Et(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=Et(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Kt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),Dt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Kt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Kt(n.href)+`"]`);if(r)return t.instance=r,Dt(r),r;var a=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Dt(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,Dt(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),Dt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,Dt(a),a):(r=n,(a=mf.get(o))&&(r=h({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Dt(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o