diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7f8d714 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug Report +about: Report a bug or unexpected behavior +title: "[BUG] " +labels: bug +assignees: '' +--- + +## Description +A clear and concise description of the bug. + +## Steps to Reproduce +1. Go to '...' +2. Click on '...' +3. Scroll down to '...' +4. See error + +## Expected Behavior +What you expected to happen. + +## Actual Behavior +What actually happened. + +## Screenshots +If applicable, add screenshots to help explain your problem. + +## Environment +- **Browser**: [e.g., Chrome 120, Firefox 121] +- **OS**: [e.g., Windows 11, macOS 14, Ubuntu 22.04] +- **Network**: [e.g., Ethereum Mainnet, Arbitrum, Localhost] + +## Additional Context +Add any other context about the problem here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0ecf165 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ +## Description + + + +## Related Issue + + + +## Type of Change + +- [ ] Bug fix +- [ ] New feature +- [ ] Documentation update +- [ ] Refactoring +- [ ] Performance improvement +- [ ] Other (please describe): + +## Changes Made + + + +## Screenshots (if applicable) + + + +## Checklist + +- [ ] I have run `npm run format:fix` and `npm run lint:fix` +- [ ] I have run `npm run typecheck` with no errors +- [ ] I have run tests with `npm run test:run` +- [ ] I have tested my changes locally +- [ ] I have updated documentation if needed +- [ ] My code follows the project's architecture patterns + +## Additional Notes + + diff --git a/.github/workflows/deploy-pr-preview.yml b/.github/workflows/deploy-pr-preview.yml index de09b11..e725df4 100644 --- a/.github/workflows/deploy-pr-preview.yml +++ b/.github/workflows/deploy-pr-preview.yml @@ -1,8 +1,7 @@ -name: Deploy PR Preview to Netlify +name: Build and Deploy PR Preview on: - workflow_dispatch: - pull_request: + pull_request_target: types: [opened, synchronize, reopened] permissions: @@ -10,11 +9,13 @@ permissions: pull-requests: write jobs: - deploy-preview: + build-and-deploy: runs-on: ubuntu-latest steps: - - name: Checkout + - name: Checkout PR head uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} - name: Setup Node.js uses: actions/setup-node@v4 @@ -22,7 +23,7 @@ jobs: node-version: '22' cache: 'npm' cache-dependency-path: './package-lock.json' - + - name: Update npm to v11 run: npm install -g npm@11 @@ -36,8 +37,8 @@ jobs: publish-dir: './dist' production-deploy: false github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: "PR Preview - ${{ github.event.pull_request.title || github.ref_name }}" - alias: pr-${{ github.event.pull_request.number || github.run_id }} + deploy-message: "PR Preview #${{ github.event.pull_request.number }}" + alias: pr-${{ github.event.pull_request.number }} enable-pull-request-comment: false enable-commit-comment: false env: @@ -45,25 +46,25 @@ jobs: NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - name: Comment PR with Preview URL - if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | + const prNumber = ${{ github.event.pull_request.number }}; const deployUrl = '${{ steps.netlify.outputs.deploy-url }}'; - const body = `šŸš€ **Preview:** ${deployUrl}\nšŸ“ **Commit:** \`${{ github.sha }}\``; - + const body = `šŸš€ **Preview:** ${deployUrl}\nšŸ“ **Commit:** \`${{ github.event.pull_request.head.sha }}\``; + // Find existing comment const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.issue.number, + issue_number: prNumber, }); - - const botComment = comments.find(comment => - comment.user.type === 'Bot' && + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && comment.body.includes('Preview:') ); - + if (botComment) { await github.rest.issues.updateComment({ owner: context.repo.owner, @@ -75,7 +76,7 @@ jobs: await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.issue.number, + issue_number: prNumber, body: body }); } diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000..8d1918a --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,42 @@ +name: E2E Tests + +on: + pull_request: + branches: [dev] + workflow_dispatch: + +jobs: + e2e: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Build application + run: npm run build + + - name: Run E2E tests + run: npm run test:e2e + env: + CI: true + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..cc28b07 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,20 @@ +name: Lint + +on: + push: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Setup Biome + uses: biomejs/setup-biome@v2 + - name: Run Biome + run: biome ci . \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0810b48..827befe 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,10 @@ ehthumbs.db .Trashes # Dev files -src/config/dev.json \ No newline at end of file +src/config/dev.json + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index cb09156..651a002 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,13 +12,18 @@ OpenScan is a trustless, open-source, standalone web-app and multi-chain blockch ```bash npm start -# Runs on http://localhost:3000 +# Runs on http://localhost:3030 ``` ### Build for Production ```bash -npm run build +# Production build +npm run build:production + +# Staging build +npm run build:staging + # Output: dist/ ``` @@ -28,10 +33,20 @@ npm run build npm run typecheck ``` -### Formatting +### Formatting and Linting ```bash +# Check formatting (dry run) +npm run format + +# Fix formatting issues automatically npm run format:fix + +# Check linting issues (dry run) +npm run lint + +# Fix linting issues automatically +npm run lint:fix ``` ### Test Environment with Local Node @@ -79,11 +94,11 @@ OpenScan follows a layered architecture with clear separation between data fetch - Returns `DataWithMetadata` when using parallel strategy, containing: - `data`: The primary result (from first successful provider) - `metadata`: Optional RPCMetadata with all provider responses, hashes, and inconsistency flags - - 30-second in-memory cache keyed by `chainId:type:identifier` + - 30-second in-memory cache keyed by `networkId:type:identifier` - Supports trace operations (debug_traceTransaction, etc.) for localhost networks only 5. **Hook Layer** (`hooks/`) - React integration - - `useDataService(chainId)`: Creates DataService instance with strategy from settings + - `useDataService(networkId)`: Creates DataService instance with strategy from settings - `useProviderSelection`: Manages user's selected RPC provider in parallel mode - `useSelectedData`: Extracts data from specific provider based on user selection @@ -166,8 +181,56 @@ OpenScan includes special support for localhost development: ## Code Style - **Biome** for formatting and linting (config: `biome.json`) + - Line width: 100 characters + - Indentation: 2 spaces + - Scope: `src/**/*.ts`, `src/**/*.tsx`, `src/**/*.json` (excludes CSS files) + - Enabled rules: All recommended Biome linting rules + - Use `npm run format:fix` to auto-format code before committing + - Use `npm run lint:fix` to auto-fix linting issues (max 1024 diagnostics shown) - **TypeScript** with strict mode (`noImplicitAny`, `noImplicitReturns`, `noUncheckedIndexedAccess`) - **React 19** with functional components and hooks +- **CSS** All styles should be on `src/styles` folder, avoid using in line component styles. + +## Coding Standards and Workflow + +### Before Committing Code + +ALWAYS run these commands before committing to ensure code quality: + +```bash +# 1. Fix formatting issues +npm run format:fix + +# 2. Fix linting issues +npm run lint:fix + +# 3. Verify type safety +npm run typecheck + +# 4. Run tests (if applicable) +npm run test:run +``` + +### Commits + +- Follow the convetional commit standard v1.0.0 +- Commit without claude attribution + +### Code Quality Requirements + +- All code must pass Biome formatting and linting checks +- All TypeScript code must pass type checking with zero errors +- Follow the 100-character line width limit +- Use 2-space indentation consistently +- Adhere to Biome's recommended linting rules +- Keep documentation up to date. + +### When Claude Code Modifies Files + +- Run `npm run format:fix` and `npm run lint:fix` after making changes +- Address any remaining linting warnings that cannot be auto-fixed +- Ensure TypeScript compilation succeeds with `npm run typecheck` +- Do not commit code with formatting, linting, or type errors ## Important Patterns @@ -187,9 +250,9 @@ OpenScan includes special support for localhost development: ### When Working with Cache -- Cache keys format: `${chainId}:${type}:${identifier}` +- Cache keys format: `${networkId}:${type}:${identifier}` - Don't cache "latest" block queries -- Clear cache when switching networks using `clearCacheForChain(chainId)` +- Clear cache when switching networks using `clearCacheForChain(networkId)` - Default timeout: 30 seconds ### RPC Strategy Switching diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f465ada --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,163 @@ +# Contributing to OpenScan + +Thank you for your interest in contributing to OpenScan! This guide will help you get started. + +## Getting Started + +### Prerequisites + +- Node.js v22 +- npm + +### Setup + +1. Fork the repository on GitHub +2. Clone your fork: + ```bash + git clone https://github.com/YOUR_USERNAME/openscan.git + cd openscan + ``` +3. Install dependencies: + ```bash + npm install + ``` +4. Start the development server: + ```bash + npm start + ``` + The app will be available at http://localhost:3030 + +## Development Workflow + +### Branch Naming + +Create feature branches from `dev` using descriptive names: + +- `feature/add-new-network` - New features +- `fix/transaction-display` - Bug fixes +- `docs/update-readme` - Documentation changes +- `refactor/data-service` - Code refactoring + +### Creating a Branch + +```bash +git checkout dev +git pull origin dev +git checkout -b feature/your-feature-name +``` + +## Code Quality Checklist + +Before submitting a pull request, you must run these commands and ensure they pass: + +```bash +# Fix formatting issues +npm run format:fix + +# Fix linting issues +npm run lint:fix + +# Verify type safety +npm run typecheck + +# Run tests +npm run test:run +``` + +All code must: + +- Pass Biome formatting and linting checks +- Pass TypeScript type checking with zero errors +- Follow the 100-character line width limit +- Use 2-space indentation consistently + +## Commit Guidelines + +We follow [Conventional Commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). + +### Commit Message Format + +``` +: + +[optional body] + +[optional footer(s)] +``` + +### Types + +- `feat`: A new feature +- `fix`: A bug fix +- `docs`: Documentation changes +- `style`: Code style changes (formatting, no logic change) +- `refactor`: Code refactoring (no feature or bug fix) +- `test`: Adding or updating tests +- `chore`: Maintenance tasks + +### Examples + +``` +feat: add support for Polygon zkEVM network +fix: correct gas estimation for L2 transactions +docs: update network configuration instructions +refactor: extract common adapter logic to base class +``` + +## Pull Request Process + +1. **Target Branch**: All PRs should target the `dev` branch +2. **Title**: Use a clear, descriptive title following commit conventions +3. **Description**: Explain what changed and why +4. **Link Issues**: Reference any related issues (e.g., "Closes #123") +5. **CI Checks**: Ensure all CI checks pass before requesting review + +When you open a PR, a template will be provided automatically. Please fill it out completely. + +## Architecture Overview + +OpenScan follows a layered architecture. For detailed information, see [CLAUDE.md](./CLAUDE.md). + +### Key Pattern: Fetcher → Adapter → Service + +1. **Fetcher Layer**: Makes raw RPC calls to blockchain nodes +2. **Adapter Layer**: Transforms RPC responses into typed domain objects +3. **Service Layer**: Orchestrates data fetching with caching + +When modifying data fetching: + +- Maintain the adapter pattern +- Test both `fallback` and `parallel` RPC strategies +- Update TypeScript types in `src/types/index.ts` if adding new fields + +## Adding a New Network + +1. Add chain ID to `src/types/index.ts` if creating new domain types +2. Add default RPC endpoints to `src/config/rpcConfig.ts` +3. Determine if network needs custom fetchers/adapters +4. If custom: create `src/services/EVM/[Network]/fetchers/` and `adapters/` +5. Update `DataService` constructor to detect chain ID +6. Add network config to `ALL_NETWORKS` in `src/config/networks.ts` +7. Add network logo to `public/` and update `logoType` in network config + +## Code Style + +- **Formatter/Linter**: Biome (see `biome.json`) +- **Line Width**: 100 characters +- **Indentation**: 2 spaces +- **TypeScript**: Strict mode enabled +- **CSS**: Place styles in `src/styles/`, avoid inline styles + +## Testing with Local Networks + +For development with local blockchain networks: + +```bash +npm run dev +``` + +This starts a Hardhat node with sample contracts and OpenScan pointing to it. + +## Questions? + +If you have questions or need help, feel free to open an issue on GitHub. diff --git a/README.md b/README.md index 29c6fe3..811bdc1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- OpenScan Logo + OpenScan Logo

# OpenScan @@ -9,7 +9,7 @@ A trustless, open-source, standalone web-app, multi-chain blockchain explorer for Ethereum, Layer 2 networks, and local development chains, allowing the direct interaction with verified smart contracts. **Official URL:** [https://openscan.eth.link/](https://openscan.eth.link/) -**GitHub Pages:** [https://openscan.github.io/explorer/](https://openscan.github.io/explorer/) +**GitHub Pages:** [https://openscan-explorer.github.io/explorer/](https://openscan.github.io/explorer/) ## Features @@ -60,7 +60,7 @@ A trustless, open-source, standalone web-app, multi-chain blockchain explorer fo ### Prerequisites -- Node.js 16+ +- Node.js v22.21.1 - npm or yarn ### Installation @@ -79,11 +79,11 @@ npm install npm start ``` -The app will open at `http://localhost:3000` +The app will open at `http://localhost:3030` ### Use with Anvil/Foundry -After following the installation steps here https://getfoundry.sh/introduction/installation/ you can just run an anvil mainnet fork with `anvil --fork-url https://reth-ethereum.ithaca.xyz/rpc` or if you run any Anvil instance on the port 8545 it would be automatically detected by Openscan, if you run Anvil on a different port make sure to change the RPC on the app settings. +After following the installation steps here you can just run an anvil mainnet fork with `anvil --fork-url https://reth-ethereum.ithaca.xyz/rpc` or if you run any Anvil instance on the port 8545 it would be automatically detected by Openscan, if you run Anvil on a different port make sure to change the RPC on the app settings. ### Use with Hardhat node @@ -102,6 +102,7 @@ bash scripts/run-test-hardhat-env.sh ``` This script will: + 1. Start a Hardhat or Anvil node on port 8545. 2. Deploy test contracts and generate sample transactions 3. Start OpenScan with only Ethereum Mainnet and Localhost networks enabled @@ -110,9 +111,15 @@ This script will: ### Build for Production ```bash -npm run build +# Production build +npm run build:production + +# Staging build +npm run build:staging ``` +> **Note:** Build scripts require bash (Linux/macOS/WSL). Windows users should use WSL or run builds via CI. + ### Type guards ```bash @@ -121,12 +128,58 @@ npm run typecheck ### Lint and prettier +```bash +npm run format:fix +``` + ```bash npm run lint:fix ``` +### End-to-End Tests + +The project uses Playwright for E2E testing against Ethereum mainnet data. + +```bash +# Run all E2E tests +npm run test:e2e + +# Run tests with UI mode (for debugging) +npm run test:e2e:ui + +# Run tests in debug mode +npm run test:e2e:debug +``` + +**Test Coverage:** + +- **Block Page** - Pre/post London blocks, hash fields, navigation +- **Transaction Page** - Legacy and EIP-1559 transactions, from/to addresses, gas info +- **Address Page** - EOA balances, ENS names, ERC20/ERC721/ERC1155 contracts +- **Token Details** - NFT metadata, properties, token URI, collection info +- **Contract Interaction** - Verified contract functions, events, verification status + +Tests run automatically on every PR via GitHub Actions. + ## Configuration +### Git pre-commit + +1. Create a new file under `.git/hooks/pre-commit` + +```bash +#!/bin/sh +set -eu + +npx @biomejs/biome check --staged --files-ignore-unknown=true --no-errors-on-unmatched +``` + +2. Make it executable + +```bash +chmod +x pre-commit +``` + ### Environment Variables #### `REACT_APP_OPENSCAN_NETWORKS` @@ -137,6 +190,8 @@ Controls which networks are displayed in the application. This is useful for lim **Default:** If not set, all supported networks are enabled. +**Note:** The Localhost network (31337) is only visible in development mode. To enable it in production/staging, explicitly include it in `REACT_APP_OPENSCAN_NETWORKS`. + **Examples:** ```bash @@ -231,15 +286,6 @@ src/ - **Type Guards** - Runtime type checking for L2-specific fields - **RPC Storage** - Persistent storage for custom RPC configurations -### Adding a New Network - -1. Add chain ID to `src/types/index.ts` -2. Add default RPC endpoints to `src/utils/rpcStorage.ts` -3. Create adapters and fetchers in `src/services/EVM/[Network]/` -4. Update `DataService` conditional logic -5. Add network card to Home page -6. Add network logo to assets folder - ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/biome.json b/biome.json index cbaf9de..698eac0 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,22 @@ { - "files": { - "includes": ["**", "!**/*.css"] - } + "files": { + "includes": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json", "!**/*.css"] + }, + "linter": { + "rules": { + "recommended": true + } + }, + "formatter": { + "lineWidth": 100, + "indentStyle": "space", + "indentWidth": 2 + }, + "assist": { + "actions": { + "source": { + "organizeImports": "off" + } + } + } } diff --git a/e2e/fixtures/arbitrum.ts b/e2e/fixtures/arbitrum.ts new file mode 100644 index 0000000..08dd045 --- /dev/null +++ b/e2e/fixtures/arbitrum.ts @@ -0,0 +1,260 @@ +// cspell:ignore arbos nitro uniswap sequencer +export const ARBITRUM = { + chainId: "42161", + + // Arbitrum One mainnet launched: August 31, 2021 + // Nitro upgrade: Block #22,207,817 (August 31, 2022) + // Block time: ~0.25 seconds (variable) + + blocks: { + // Genesis block - Arbitrum One launch + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "288,000,000", + hash: "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442", + }, + // Nitro upgrade block (August 31, 2022) + // Massive upgrade - new architecture, faster, cheaper + "22207817": { + number: 22207817, + txCount: 0, + gasUsed: "0", + size: "553 bytes", + upgradeNote: "Nitro upgrade - new architecture", + }, + // Block 100,000,000 (June 11, 2023) + // Post-Nitro, pre-ArbOS 11 + "100000000": { + number: 100000000, + txCount: 4, + gasUsed: "2,059,307", + gasUsedPercent: "0.0%", // Very large gas limit on Arbitrum + gasLimit: "1,125,899,906,842,624", + size: "1,225 bytes", + baseFeePerGas: "0.1 Gwei", + // Fee recipient is the sequencer address + feeRecipientPartial: "0xa4b00000", + // More details section + hash: "0xb5aeb03c97e45c59596b70905d077663bccfea4533bf3b2c3264871725ea86a8", + parentHash: "0x1e3abf9d4545f0ed50137b68e1a5044fcad217d492ba2d92585cc101bbf03c9d", + nonce: "0x00000000000dd6ce", + }, + // Block 200,000,000 (April 11, 2024) + // Post-ArbOS 20 Atlas (Dencun support) + "200000000": { + number: 200000000, + txCount: 2, + gasUsed: "55,132", + gasUsedPercent: "0.0%", + gasLimit: "1,125,899,906,842,624", + size: "801 bytes", + baseFeePerGas: "0.01 Gwei", + feeRecipientPartial: "0xa4b00000", + // More details section + hash: "0xfbb039d0d0e358b4d65f3df3058026fe5576beee3ed1fa2c1ad677d2efe0f3c1", + parentHash: "0x76bb92461bfba3e3d0dffd589b47170979891096d39f0173b41b7ce25bb9b5b1", + }, + // Block 300,000,000 (January 28, 2025) + // Post-ArbOS 32 Bianca (Stylus) + "300000000": { + number: 300000000, + txCount: 4, + gasUsed: "913,478", + gasUsedPercent: "0.0%", + gasLimit: "1,125,899,906,842,624", + size: "1,073 bytes", + baseFeePerGas: "0.01 Gwei", + feeRecipientPartial: "0xa4b00000", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // Uniswap V3 swap via multicall - Legacy transaction + "0x87815a816c02b5a563a026e4a37d423734204b50972e75284b62f05e4134ae44": { + hash: "0x87815a816c02b5a563a026e4a37d423734204b50972e75284b62f05e4134ae44", + type: 0, // Legacy transaction + from: "0x6Cd9642Af3991e761C2785f9C958F148d6AeA4F8", + to: "0xE592427A0AEce92De3Edee1F18E0157C05861564", // Uniswap V3 Router + value: "0x31c7fb4fe7a0717", // ~0.224 ETH + blockNumber: 416908399, + gas: "1,200,000", + gasUsed: "143,833", + gasPrice: "0.01 Gwei", + nonce: 82, + status: "success" as const, + hasInputData: true, + method: "multicall", + }, + + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) + // ============================================ + + // USDC transfer (EIP-1559) + "0x160687cbf03f348cf36997dbab53abbd32d91af5971bccac4cfa1577da27607e": { + hash: "0x160687cbf03f348cf36997dbab53abbd32d91af5971bccac4cfa1577da27607e", + type: 2, // EIP-1559 + from: "0xb7990f2266a97A1f06b0F1828b2fE46B3582456F", + to: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC + value: "0x0", + blockNumber: 416908475, + gas: "45,632", + gasUsed: "40,235", + maxFeePerGas: "0.01 Gwei", + maxPriorityFeePerGas: "0.000159827 Gwei", + nonce: 4, + status: "success" as const, + hasInputData: true, + method: "transfer", + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // Native USDC on Arbitrum - Circle's native USDC + usdc: { + address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // Bridged USDC.e (legacy) + usdce: { + address: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + type: "erc20" as const, + symbol: "USDC.e", + name: "Bridged USDC", + decimals: 6, + }, + // WETH on Arbitrum + weth: { + address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // ARB token - Arbitrum governance token + arb: { + address: "0x912CE59144191C1204E64559FE8253a0e49E6548", + type: "erc20" as const, + symbol: "ARB", + name: "Arbitrum", + decimals: 18, + }, + // GMX token + gmx: { + address: "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a", + type: "erc20" as const, + symbol: "GMX", + name: "GMX", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // Uniswap V3 Router + uniswapV3Router: { + address: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + type: "contract" as const, + name: "Uniswap V3: Swap Router", + }, + // Uniswap Universal Router + uniswapUniversalRouter: { + address: "0x4C60051384bd2d3C01bfc845Cf5F4b44bcbE9de5", + type: "contract" as const, + name: "Uniswap: Universal Router", + }, + // GMX Vault + gmxVault: { + address: "0x489ee077994B6658eAfA855C308275EAd8097C4A", + type: "contract" as const, + name: "GMX: Vault", + }, + // GMX Position Router + gmxPositionRouter: { + address: "0xb87a436B93fFe9D75c5cFA7baCFFF96430b09868", + type: "contract" as const, + name: "GMX: Position Router", + }, + + // ============================================ + // ARBITRUM SYSTEM CONTRACTS (precompiles) + // ============================================ + + // ArbSys - Arbitrum system precompile + arbSys: { + address: "0x0000000000000000000000000000000000000064", + type: "contract" as const, + name: "ArbSys", + }, + // ArbRetryableTx - Retryable ticket system + arbRetryableTx: { + address: "0x000000000000000000000000000000000000006E", + type: "contract" as const, + name: "ArbRetryableTx", + }, + // NodeInterface - Node queries + nodeInterface: { + address: "0x00000000000000000000000000000000000000C8", + type: "contract" as const, + name: "NodeInterface", + }, + }, + + // ArbOS upgrade history + upgrades: { + nitro: { + block: 22207817, + date: "2022-08-31T14:32:22Z", + description: "Nitro upgrade - new architecture, faster, cheaper", + }, + arbos11: { + timestamp: 1708809673, + date: "2024-02-24T20:01:13Z", + description: "Shanghai EVM support, PUSH0 opcode", + }, + arbos20Atlas: { + timestamp: 1710424089, + date: "2024-03-14T13:48:09Z", + description: "Dencun support (EIP-4844 blobs)", + }, + arbos31Bianca: { + timestamp: 1725386400, + date: "2024-09-03T17:00:00Z", + description: "Stylus prep", + }, + arbos32Bianca: { + timestamp: 1727239050, + date: "2024-09-25T02:37:30Z", + description: "Stylus activation (WASM VM alongside EVM)", + }, + bold: { + timestamp: 1739368811, + date: "2025-02-12T14:00:11Z", + description: "BoLD dispute resolution", + }, + arbos40Callisto: { + timestamp: 1750197383, + date: "2025-06-17T22:56:23Z", + description: "Pectra support (EIP-7702)", + }, + arbos51Dia: { + timestamp: 1736355600, + date: "2026-01-08T17:00:00Z", + description: "Fusaka support (pending)", + }, + }, +}; diff --git a/e2e/fixtures/base.ts b/e2e/fixtures/base.ts new file mode 100644 index 0000000..5b59b60 --- /dev/null +++ b/e2e/fixtures/base.ts @@ -0,0 +1,228 @@ +// cspell:ignore aerodrome superchain sequencer +export const BASE = { + chainId: "8453", + + // Base genesis: June 15, 2023 (timestamp 1686789347) + // Block time: 2 seconds + // Upgrades follow Superchain-wide activation timestamps + + blocks: { + // Genesis block - Base mainnet launch (June 15, 2023) + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "30,000,000", + hash: "0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 1,000,000 - Early Base block (July 8, 2023) + // 1 transaction, minimal gas usage + "1000000": { + number: 1000000, + txCount: 1, + gasUsed: "46,913", + gasUsedPercent: "0.2%", + gasLimit: "30,000,000", + size: "869 bytes", + baseFeePerGas: "50 wei", + feeRecipientPartial: "0x42000000", + }, + // Block 10,000,000 - Pre-Ecotone (February 1, 2024) + // Before EIP-4844 blob support + "10000000": { + number: 10000000, + txCount: 11, + gasUsed: "979,572", + gasUsedPercent: "3.3%", + gasLimit: "30,000,000", + size: "4,718 bytes", + baseFeePerGas: "265 wei", + feeRecipientPartial: "0x42000000", + }, + // Block 25,000,000 - Post-Holocene (January 13, 2025) + // Recent block with increased gas limit + "25000000": { + number: 25000000, + txCount: 248, + gasUsed: "59,984,755", + gasUsedPercent: "25.0%", + gasLimit: "240,000,000", + size: "91,675 bytes", + baseFeePerGas: "0.020162741 Gwei", + feeRecipientPartial: "0x42000000", + }, + }, + + transactions: { + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) - Standard on Base + // ============================================ + + // Aerodrome DEX swap transaction - swapExactTokensForTokens + "0x961cf2c57f006d8c6fdbe266b2ef201159dd135dc560155e8c16d307ee321681": { + hash: "0x961cf2c57f006d8c6fdbe266b2ef201159dd135dc560155e8c16d307ee321681", + type: 2, + from: "0xF9b6a1EB0190bf76274B0876957Ee9F4f508Af41", + to: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43", // Aerodrome Router + value: "0x0", + gasUsed: "176,474", + status: "success" as const, + hasInputData: true, + method: "swapExactTokensForTokens", + }, + + // USDC transferWithAuthorization - ERC20 interaction + "0x6b212a5069286d710f388b948364452d28b8c33e0f39b8f50b394ff4deff1f03": { + hash: "0x6b212a5069286d710f388b948364452d28b8c33e0f39b8f50b394ff4deff1f03", + type: 2, + from: "0x3A70788150c7645a21b95b7062ab1784D3cc2104", + to: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC + value: "0x0", + gasUsed: "86,212", + status: "success" as const, + hasInputData: true, + method: "transferWithAuthorization", + }, + + // ============================================ + // SYSTEM TRANSACTIONS - OP Stack specific + // ============================================ + + // L1Block setL1BlockValues - System transaction (Type 126) + // First transaction in block 1 - sets L1 block attributes + "0x68736fb400dc3b69ab1c4c2cbe75a600aa7ba7cd8d025797ebd8a0108955c91f": { + hash: "0x68736fb400dc3b69ab1c4c2cbe75a600aa7ba7cd8d025797ebd8a0108955c91f", + type: 126, // Deposit transaction type + from: "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", // System address + to: "0x4200000000000000000000000000000000000015", // L1Block + value: "0x0", + gasUsed: "64,013", + status: "success" as const, + hasInputData: true, + method: "setL1BlockValues", + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // USDC on Base - Circle's native USDC + usdc: { + address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // USDbC - Bridged USDC (legacy) + usdbc: { + address: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", + type: "erc20" as const, + symbol: "USDbC", + name: "USD Base Coin", + decimals: 6, + }, + // WETH on Base (predeploy) + weth: { + address: "0x4200000000000000000000000000000000000006", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // AERO token - Aerodrome governance token + aero: { + address: "0x940181a94A35A4569E4529A3CDFb74e38FD98631", + type: "erc20" as const, + symbol: "AERO", + name: "Aerodrome", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // Aerodrome Router - Main DEX on Base + aerodromeRouter: { + address: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43", + type: "contract" as const, + name: "Aerodrome: Router", + }, + + // ============================================ + // SYSTEM CONTRACTS (OP Stack predeploys) + // ============================================ + + // SequencerFeeVault - Receives transaction fees (fee recipient) + sequencerFeeVault: { + address: "0x4200000000000000000000000000000000000011", + type: "contract" as const, + name: "SequencerFeeVault", + }, + // L2CrossDomainMessenger - Bridge messaging + l2CrossDomainMessenger: { + address: "0x4200000000000000000000000000000000000007", + type: "contract" as const, + name: "L2CrossDomainMessenger", + }, + // L2StandardBridge - Token bridging + l2StandardBridge: { + address: "0x4200000000000000000000000000000000000010", + type: "contract" as const, + name: "L2StandardBridge", + }, + // GasPriceOracle - L1 fee calculation + gasPriceOracle: { + address: "0x420000000000000000000000000000000000000F", + type: "contract" as const, + name: "GasPriceOracle", + }, + // L1Block - L1 block attributes + l1Block: { + address: "0x4200000000000000000000000000000000000015", + type: "contract" as const, + name: "L1Block", + }, + }, + + // Upgrade timestamps (Unix) for reference + upgrades: { + canyon: { + timestamp: 1704992401, + date: "2024-01-11T16:00:01Z", + }, + delta: { + timestamp: 1708560000, + date: "2024-02-22T00:00:00Z", + }, + ecotone: { + timestamp: 1710374401, + date: "2024-03-14T00:00:01Z", + description: "EIP-4844 blob support, ~90% fee reduction", + }, + fjord: { + timestamp: 1720627201, + date: "2024-07-10T16:00:01Z", + description: "Brotli compression, RIP-7212 secp256r1 precompile", + }, + granite: { + timestamp: 1726070401, + date: "2024-09-11T16:00:01Z", + description: "Gas optimizations, permissionless fault proofs", + }, + holocene: { + timestamp: 1736445601, + date: "2025-01-09T18:00:01Z", + description: "Stricter derivation, EIP-1559 configurability", + }, + isthmus: { + timestamp: 1746806401, + date: "2025-05-09T16:00:01Z", + description: "Pectra L2 support", + }, + }, +}; diff --git a/e2e/fixtures/bsc.ts b/e2e/fixtures/bsc.ts new file mode 100644 index 0000000..97d80b7 --- /dev/null +++ b/e2e/fixtures/bsc.ts @@ -0,0 +1,360 @@ +// cspell:ignore pancakeswap binance busd wbnb staking validator +export const BSC = { + chainId: "56", + + // BNB Smart Chain mainnet launch: September 1, 2020 + // Original block time: 3 seconds + // After Lorentz (April 2025): 1.5 seconds + // After Maxwell (June 30, 2025): 0.75 seconds + // Consensus: Proof-of-Staked-Authority (PoSA) with 21+ validators + + blocks: { + // Genesis block - BSC mainnet launch (September 1, 2020) + // Contains initial validator setup and seed fund distribution + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "30,000,000", + hash: "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 10,000,000 (December 2021) + // Pre-Euler, early BSC activity + "10000000": { + number: 10000000, + gasUsed: "36,309,493", + gasUsedPercent: "42.8%", + gasLimit: "84,934,464", + hash: "0xd08dca8c7d87780ca6f2faed2e12508d431939f7d7c3fa0d052f0744e1a47e55", + parentHash: "0x6b77d2519fc680931d57729aac79a1a30c9c7a9e684499d7ed52fa800f9c799c", + }, + // Block 20,000,000 (August 2022) + // Post-Euler upgrade + "20000000": { + number: 20000000, + txCount: 321, + gasUsed: "26,602,654", + gasUsedPercent: "28.3%", + gasLimit: "94,082,549", + hash: "0xba219d00ab4ea174ead8efa90c80e9d9f9990e221bf8e7da1881ec210edbb879", + parentHash: "0xafbeac4631e64db3bb1a23d318b3fd9cb6c81b2d9e8bc50c5c46d25eb2aa6dfc", + }, + // Block 30,000,000 (July 2023) + // Post-Luban, fast finality enabled + "30000000": { + number: 30000000, + gasUsed: "21,238,276", + gasUsedPercent: "15.2%", + gasLimit: "140,001,959", + hash: "0x0fe1b87f3d62477a866bbd2327139b884c0dc057f11dc273f8bed34fe699efbd", + parentHash: "0x577bde40f5e997be68cc6b2e98ae97024b86e6a15ac00b6a515dc373ec9d57c4", + }, + // Block 40,000,000 (June 2024) + // Post-Feynman, after BNB Chain Fusion + "40000000": { + number: 40000000, + txCount: 107, + gasUsed: "11,270,569", + gasUsedPercent: "8.1%", + gasLimit: "139,456,038", + hash: "0x095f88c4f4855eab7bc6bb7161ade81adf7d60e11804d40197e4388227d1eddf", + parentHash: "0x5743a53c47bccbc29dad622be65ac9652b7980eefc9776e1e328b190c546851f", + }, + // Block 50,000,000 (May 2025) + // Post-Maxwell, 0.75s block time + "50000000": { + number: 50000000, + txCount: 358, + gasUsed: "50,409,444", + gasUsedPercent: "72.0%", + gasLimit: "70,000,000", + hash: "0x66132739a8759cd2fc911fb31eee5a6beaefaa25cf25385b6dec775f7ba02192", + parentHash: "0x551190b5d3d2a5de87becc1612c03a2568e7fc1c865a0e3d68776b16573c2789", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // Contract interaction from block 20,000,000 - Legacy transaction + // Real transaction with verified on-chain data + "0xad5c9b13688627d670985d68a5be0fadd5f0e34d3ff20e35c655ef4bceec7e7c": { + hash: "0xad5c9b13688627d670985d68a5be0fadd5f0e34d3ff20e35c655ef4bceec7e7c", + type: 0, // Legacy transaction + from: "0x67e83034d88c665c661c77f8c9a1a6464224ee9d", + to: "0x0303d52057efef51eeea9ad36bc788df827f183d", + value: "0x0", + blockNumber: 20000000, + gas: "700,000", + gasUsed: "40,462", + gasPrice: "25.21 Gwei", + nonce: 266694, + position: 0, + status: "success" as const, + hasInputData: true, + }, + + // DEX swap from block 40,000,000 - Legacy transaction + // swapExactTokensForTokens on a DEX router + "0x0e3384ad2350d20921190b15e29305ed08eecfe97de975b6e015a6c6d476a90a": { + hash: "0x0e3384ad2350d20921190b15e29305ed08eecfe97de975b6e015a6c6d476a90a", + type: 0, // Legacy transaction + from: "0xbb6694f2ce58d9c83b35ad65da6f6423756e0585", + to: "0xeddb16da43daed83158417955dc0c402c61e7e7d", + value: "0x0", + blockNumber: 40000000, + gas: "257,348", + gasUsed: "170,460", + gasPrice: "7 Gwei", + nonce: 39, + position: 0, + status: "success" as const, + hasInputData: true, + method: "swapExactTokensForTokens", + }, + + // DEX aggregator swap from block 50,000,000 - Legacy transaction + // Complex multi-hop swap with many token transfers + "0x874a90a47bc3140adbffff0f4b89da4bea48f9420f97bc5a50e2e478d9a06176": { + hash: "0x874a90a47bc3140adbffff0f4b89da4bea48f9420f97bc5a50e2e478d9a06176", + type: 0, // Legacy transaction + from: "0x05a13e324ac38d76e06dd95f72194f1570f5fa7d", + to: "0x22444f9024367c1313613d54efa31f0aaf8627d7", + value: "0x0", + blockNumber: 50000000, + gas: "1,397,513", + gasUsed: "906,323", + gasPrice: "0.1 Gwei", + nonce: 20548, + position: 2, + status: "success" as const, + hasInputData: true, + }, + }, + + addresses: { + // ============================================ + // ERC20/BEP20 TOKENS + // ============================================ + + // Wrapped BNB (WBNB) + wbnb: { + address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + type: "erc20" as const, + symbol: "WBNB", + name: "Wrapped BNB", + decimals: 18, + }, + // USDT (Binance-Peg BSC-USD) + usdt: { + address: "0x55d398326f99059fF775485246999027B3197955", + type: "erc20" as const, + symbol: "USDT", + name: "Binance-Peg BSC-USD", + decimals: 18, + }, + // BUSD (Binance-Peg BUSD Token) + busd: { + address: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", + type: "erc20" as const, + symbol: "BUSD", + name: "Binance-Peg BUSD Token", + decimals: 18, + }, + // USDC (Binance-Peg USD Coin) + usdc: { + address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + type: "erc20" as const, + symbol: "USDC", + name: "Binance-Peg USD Coin", + decimals: 18, + }, + // CAKE (PancakeSwap Token) + cake: { + address: "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", + type: "erc20" as const, + symbol: "Cake", + name: "PancakeSwap Token", + decimals: 18, + }, + // DAI (Binance-Peg Dai Token) + dai: { + address: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3", + type: "erc20" as const, + symbol: "DAI", + name: "Binance-Peg Dai Token", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // PancakeSwap Router v2 - Main DEX on BSC + pancakeswapRouterV2: { + address: "0x10ED43C718714eb63d5aA57B78B54704E256024E", + type: "contract" as const, + name: "PancakeSwap: Router v2", + }, + // PancakeSwap Factory v2 + pancakeswapFactoryV2: { + address: "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", + type: "contract" as const, + name: "PancakeSwap: Factory v2", + }, + // PancakeSwap Universal Router + pancakeswapUniversalRouter: { + address: "0x1A0A18AC4BECDDbd6389559687d1A73d8927E416", + type: "contract" as const, + name: "PancakeSwap: Universal Router", + }, + + // ============================================ + // SYSTEM CONTRACTS (BSC predeploys) + // ============================================ + + // Validator Set Contract - Manages validator elections + validatorSet: { + address: "0x0000000000000000000000000000000000001000", + type: "contract" as const, + name: "BSC: Validator Set", + }, + // Slash Contract - Handles validator slashing + slashContract: { + address: "0x0000000000000000000000000000000000001001", + type: "contract" as const, + name: "BSC: Slash Contract", + }, + // System Reward Contract - Distributes system rewards + systemReward: { + address: "0x0000000000000000000000000000000000001002", + type: "contract" as const, + name: "BSC: System Reward", + }, + // Light Client Contract + lightClient: { + address: "0x0000000000000000000000000000000000001003", + type: "contract" as const, + name: "BSC: Light Client", + }, + // Token Hub - Cross-chain token management + tokenHub: { + address: "0x0000000000000000000000000000000000001004", + type: "contract" as const, + name: "BSC: Token Hub", + }, + // Relayer Hub - Cross-chain relayer management + relayerHub: { + address: "0x0000000000000000000000000000000000001006", + type: "contract" as const, + name: "BSC: Relayer Hub", + }, + // Staking Contract - Manages BNB staking + stakeHub: { + address: "0x0000000000000000000000000000000000002002", + type: "contract" as const, + name: "BSC: Stake Hub", + }, + // Governor Contract - Governance + governor: { + address: "0x0000000000000000000000000000000000002004", + type: "contract" as const, + name: "BSC: Governor", + }, + + // ============================================ + // STAKING CONTRACTS + // ============================================ + + // PancakeSwap Main Staking Contract + pancakeswapStaking: { + address: "0x73feaa1eE314F8c655E354234017bE2193C9E24E", + type: "contract" as const, + name: "PancakeSwap: Main Staking Contract", + }, + // PancakeSwap Cake Pool + pancakeswapCakePool: { + address: "0x45c54210128a065de780C4B0Df3d16664f7f859e", + type: "contract" as const, + name: "PancakeSwap: Cake Pool", + }, + }, + + // Upgrade block heights and timestamps for reference + upgrades: { + bruno: { + blockHeight: 13082000, + timestamp: 1638259200, + date: "2021-11-30T08:00:00Z", + description: "Real-time BNB burning mechanism (BEP-95)", + }, + euler: { + blockHeight: 18907621, + timestamp: 1655884800, + date: "2022-06-22T08:00:00Z", + description: "Increased validators, enhanced decentralization (BEP-127/131)", + }, + planck: { + blockHeight: 27281024, + timestamp: 1681274400, + date: "2023-04-12T05:30:00Z", + description: "Cross-chain security enhancements (ICS23)", + }, + luban: { + blockHeight: 29020050, + timestamp: 1686516600, + date: "2023-06-11T21:30:00Z", + description: "Fast finality mechanism capability (BEP-126)", + }, + plato: { + blockHeight: 30720096, + timestamp: 1691668800, + date: "2023-08-10T12:00:00Z", + description: "Fast finality fully enabled (BEP-126)", + }, + hertz: { + blockHeight: 31302048, + timestamp: 1693382400, + date: "2023-08-30T07:30:00Z", + description: "Berlin/London EIPs for EVM compatibility", + }, + feynman: { + timestamp: 1713430140, + date: "2024-04-18T05:49:00Z", + description: "BNB Chain Fusion, validators increased to 45", + }, + tycho: { + timestamp: 1718668800, + date: "2024-06-18T00:00:00Z", + description: "Blob transactions support (BEP-336)", + }, + bohr: { + timestamp: 1727350800, + date: "2024-09-26T12:00:00Z", + description: "Consecutive block production (BEP-341)", + }, + pascal: { + timestamp: 1741996800, + date: "2025-03-15T00:00:00Z", + description: "Smart contract wallets (EIP-7702), BLS12-381", + }, + lorentz: { + timestamp: 1745366400, + date: "2025-04-22T00:00:00Z", + description: "Block time reduced to 1.5 seconds", + }, + maxwell: { + timestamp: 1751270400, + date: "2025-06-30T00:00:00Z", + description: "Block time reduced to 0.75 seconds (BEP-524/563/564)", + }, + fermi: { + timestamp: 1736825400, + date: "2026-01-14T02:30:00Z", + description: "Block time reduced to 0.45 seconds (BEP-619/590)", + }, + }, +}; diff --git a/e2e/fixtures/mainnet.ts b/e2e/fixtures/mainnet.ts new file mode 100644 index 0000000..6f1c334 --- /dev/null +++ b/e2e/fixtures/mainnet.ts @@ -0,0 +1,446 @@ +// cspell:ignore vitalik beaverbuild dencun bayc rarible rari +export const MAINNET = { + chainId: "1", + + blocks: { + // Genesis block - special case with no transactions + "0": { + number: 0, + txCount: 0, + }, + // Block 10,000 - Pre-London, no transactions + "10000": { + number: 10000, + txCount: 0, + gasUsed: "0", + gasUsedPercent: "0.0%", + gasLimit: "5,000", + size: "541 bytes", + feeRecipientPartial: "0xbf71642d", + difficulty: "598,426,820,912", + totalDifficulty: "598,426,820,912", + extraData: "Geth/v1.0.0/windows/go1.4.2", + // More details section + hash: "0xdc2d938e4cd0a149681e9e04352953ef5ab399d59bcd5b0357f6c0797470a524", + parentHash: "0xb9ecd2df84ee2687efc0886f5177f6674bad9aeb73de9323e254e15c5a34fc93", + stateRoot: "0x4de830f589266773eae1a1caa88d75def3f3a321fbd9aeb89570a57c6e7f3dbb", + transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + nonce: "0xb75e5f372524d34c", + mixHash: "0xc6bf383db032101cc2101543db260602b709e5d9e38444bb71b680777185448b", + sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + }, + // Block 1,000,000 - Pre-London, with 2 transactions + "1000000": { + number: 1000000, + txCount: 2, + gasUsed: "50,244", + gasUsedPercent: "1.6%", + gasLimit: "3,141,592", + size: "768 bytes", + feeRecipientPartial: "0x2a65aca4", + difficulty: "12,549,332,509,227", + totalDifficulty: "12,549,332,509,227", + extraData: "0xd783010303844765746887676f312e352e31856c696e7578", + // More details section + hash: "0x8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e", + parentHash: "0xb4fbadf8ea452b139718e2700dc1135cfc81145031c84b7ab27cd710394f7b38", + stateRoot: "0x0e066f3c2297a5cb300593052617d1bca5946f0caa0635fdb1b85ac7e5236f34", + transactionsRoot: "0x65ba887fcb0826f616d01f736c1d2d677bcabde2f7fc25aa91cfbc0b3bad5cb3", + receiptsRoot: "0x20e3534540caf16378e6e86a2bf1236d9f876d3218fbc03958e6db1c634b2333", + nonce: "0xcd4c55b941cf9015", + mixHash: "0x92c4129a0ae2361b452a9edeece55c12eceeab866316195e3d87fc1b005b6645", + sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + }, + // Block 20,000,000 - Post-London (EIP-1559), with base fee and withdrawals + "20000000": { + number: 20000000, + txCount: 134, + withdrawals: 16, + gasUsed: "11,089,692", + gasUsedPercent: "37.0%", + gasLimit: "30,000,000", + size: "51,097 bytes", + feeRecipientPartial: "0x95222290", + baseFeePerGas: "4.936957716 Gwei", + burntFees: "0.054749340487 ETH", + extraData: "beaverbuild.org", + // More details section + hash: "0xd24fd73f794058a3807db926d8898c6481e902b7edb91ce0d479d6760f276183", + parentHash: "0xb390d63aac03bbef75de888d16bd56b91c9291c2a7e38d36ac24731351522bd1", + stateRoot: "0x68421c2c599dc31396a09772a073fb421c4bd25ef1462914ef13e5dfa2d31c23", + transactionsRoot: "0xf0280ae7fd02f2b9684be8d740830710cd62e4869c891c3a0ead32ea757e70a3", + receiptsRoot: "0xb39f9f7a13a342751bd2c575eca303e224393d4e11d715866b114b7e824da608", + withdrawalsRoot: "0xf0747de0368fb967ede9b81320a5b01a4d85b3d427e8bc8e96ff371478d80e76", + nonce: "0x0000000000000000", + mixHash: "0x85175443c2889afcb52288e0fa8804b671e582f9fd416071a70642d90c7dc0db", + sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + }, + // Block 12,964,999 - Last block before London hard fork (no base fee) + "12964999": { + number: 12964999, + txCount: 145, + gasUsed: "15,026,712", + gasUsedPercent: "100.0%", + gasLimit: "15,029,237", + size: "80,836 bytes", + feeRecipientPartial: "0x3ecef08d", + extraData: "vir1", + difficulty: "7,742,493,487,903,256", + totalDifficulty: "7,742,493,487,903,256", + // More details section + hash: "0x3de6bb3849a138e6ab0b83a3a00dc7433f1e83f7fd488e4bba78f2fe2631a633", + parentHash: "0x8e2b6ba8d440307457807fe9bbe1d3ef330ab12177166f69f8d4f7186e396de7", + stateRoot: "0x4035f600ba18453e0e4506b980180424c8f1853cc5dffea0be3e960993b7f828", + transactionsRoot: "0x113e7f3abfe0d307a0a945c3452fae7e34176d2432d5f59becd3b2ca2a3acabf", + receiptsRoot: "0x89b5c46add2ad67cb815fac58475de67e7ed1557477f980d37605b5d75f5efc7", + logsBloom: + "0x5ca74187e553152f19d432f6f420ba370956018791d04ce2013d6245fcc9f581823f6d58e404b7e0d545dafb3bad01100ae785e64bb4e9516d4385e212a668da58685120b002b33ecae2da1fb08877e4193c9c470e6c3309524e7d7b907f05e3be19c340bb029fa2c596fc014230bec0d233dde9f95ba6e7834573faa22d4faad9e5a17f9ed05098b3d1d3510982b83f5344cc956d61b53dbf7509f92d5a3bb2b7de5e6822213a3322b56fb49806b7a0341e4e1000b630231f739684dcbfff1d5b4016ee75ab4c4a29d29ff7c76b1de47f4df308658b4a52d26228d638d4ec600230aa923d621b68f8a7174443acc020c5a1ca307ff081f81bc8cdbeea051a7d", + nonce: "0xa3de6d1d51ea8f7d", + mixHash: "0x069f4780d57aaa74ae768c2948afaf9f5c03d26e59ccc9fd93092af8a48bed5c", + sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + }, + // Block 12,965,000 - London hard fork block (EIP-1559) + "12965000": { + number: 12965000, + txCount: 259, + gasUsed: "30,025,257", + gasUsedPercent: "100.0%", + gasLimit: "30,029,122", + size: "137,049 bytes", + feeRecipientPartial: "0x77777882", + baseFeePerGas: "1.000000000 Gwei", + burntFees: "0.030025257000 ETH", + extraData: "https://www.kryptex.org", + difficulty: "7,742,494,561,645,080", + totalDifficulty: "7,742,494,561,645,080", + // More details section + hash: "0x9b83c12c69edb74f6c8dd5d052765c1adf940e320bd1291696e6fa07829eee71", + parentHash: "0x3de6bb3849a138e6ab0b83a3a00dc7433f1e83f7fd488e4bba78f2fe2631a633", + stateRoot: "0x41cf6e8e60fd087d2b00360dc29e5bfb21959bce1f4c242fd1ad7c4da968eb87", + transactionsRoot: "0xdfcb68d3a3c41096f4a77569db7956e0a0e750fad185948e54789ea0e51779cb", + receiptsRoot: "0x8a8865cd785e2e9dfce7da83aca010b10b9af2abbd367114b236f149534c821d", + logsBloom: + "0x24e74ad77d9a2b27bdb8f6d6f7f1cffdd8cfb47fdebd433f011f7dfcfbb7db638fadd5ff66ed134ede2879ce61149797fbcdf7b74f6b7de153ec61bdaffeeb7b59c3ed771a2fe9eaed8ac70e335e63ff2bfe239eaff8f94ca642fdf7ee5537965be99a440f53d2ce057dbf9932be9a7b9a82ffdffe4eeee1a66c4cfb99fe4540fbff936f97dde9f6bfd9f8cefda2fc174d23dfdb7d6f7dfef5f754fe6a7eec92efdbff779b5feff3beafebd7fd6e973afebe4f5d86f3aafb1f73bf1e1d0cdd796d89827edeffe8fb6ae6d7bf639ec5f5ff4c32f31f6b525b676c7cdf5e5c75bfd5b7bd1928b6f43aac7fa0f6336576e5f7b7dfb9e8ebbe6f6efe2f9dfe8b3f56", + nonce: "0xb223da049adf2216", + mixHash: "0x9620b46a81a4795cf4449d48e3270419f58b09293a5421205f88179b563f815a", + sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) - Pre-EIP-2718 + // ============================================ + + // First ever ETH transfer (block 46147, mined Aug 7, 2015) + // Type 0 - Legacy transaction with gasPrice + "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060": { + hash: "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", + type: 0, + from: "0xa1e4380a3b1f749673e270229993ee55f35663b4", + to: "0x5df9b87991262f6ba471f09758cde1c0fc1de734", + value: "0x7a69", // 31337 wei + blockNumber: 46147, + gas: "21,000", + gasPrice: "50,000,000,000,000", // 50,000 Gwei (early Ethereum) + gasUsed: "21,000", + nonce: 0, + status: "success" as const, + // No maxFeePerGas or maxPriorityFeePerGas (pre-EIP-1559) + }, + + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) - Post-London + // ============================================ + + // Type 2 EIP-1559 transaction from block 20,000,000 + "0xbb4b3fc2b746877dce70862850602f1d19bd890ab4db47e6b7ee1da1fe578a0d": { + hash: "0xbb4b3fc2b746877dce70862850602f1d19bd890ab4db47e6b7ee1da1fe578a0d", + type: 2, + from: "0xae2fc483527b8ef99eb5d9b44875f005ba1fae13", + to: "0x6b75d8af000000e20b7a7ddf000ba900b4009a80", + value: "0x3e987a00", // ~1.05 ETH + blockNumber: 20000000, + gas: "458,541", + gasUsed: "321,491", + maxFeePerGas: "4.936957716 Gwei", + maxPriorityFeePerGas: "0 Gwei", + effectiveGasPrice: "4.936957716 Gwei", + nonce: 2756766, + status: "success" as const, + hasInputData: true, + // No gasPrice field in Type 2 transactions + }, + + // USDC approval transaction - common ERC20 interaction (Type 2) + "0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef": { + hash: "0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef", + type: 2, + from: "0x28C6c06298d514Db089934071355E5743bf21d60", + to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + blockNumber: 15537394, + status: "success" as const, + hasInputData: true, + }, + + // ============================================ + // BLOB TRANSACTIONS (Type 3) - Post-Dencun (EIP-4844) + // ============================================ + + // Type 3 Blob transaction from block 21,000,000 (Base L2 batch submission) + "0x7ad8a2d854221c024bc84449c0eb756c81b8db95d6774765189305e28b1c029a": { + hash: "0x7ad8a2d854221c024bc84449c0eb756c81b8db95d6774765189305e28b1c029a", + type: 3, + from: "0x5050f69a9786f081509234f1a7f4684b5e5b76c9", + to: "0xff00000000000000000000000000000000008453", // Base L2 batch inbox + value: "0x0", + blockNumber: 21000000, + gas: "21,000", + gasUsed: "21,000", + maxFeePerGas: "36.636641947 Gwei", + maxPriorityFeePerGas: "2.012051071 Gwei", + effectiveGasPrice: "20.169830720 Gwei", + // Blob-specific fields + maxFeePerBlobGas: "1 Gwei", + blobGasUsed: "655,360", // 5 blobs * 131,072 bytes + blobGasPrice: "1 wei", + blobCount: 5, + nonce: 556803, + status: "success" as const, + }, + + // ============================================ + // FAILED TRANSACTIONS + // ============================================ + + // Failed transaction (status 0x0) - reverted contract call + "0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a": { + hash: "0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a", + type: 0, + from: "0x58eb28a67731c570ef827c365c89b5751f9e6b0a", + to: "0xbf4ed7b27f1d666546e30d74d50d173d20bca754", + value: "0x0", + blockNumber: 2166348, + gas: "150,000", + gasUsed: "150,000", // All gas consumed on failure + gasPrice: "21,000,000,000", // 21 Gwei + nonce: 30, + status: "failed" as const, + hasInputData: true, // Contains function selector 0x3ccfd60b (withdraw) + }, + + // ============================================ + // CONTRACT CREATION TRANSACTIONS + // ============================================ + + // Contract creation (to: null) - Type 2 EIP-1559 + "0x452dd11bf2cac23a2c366d0adae6cc6451a3665c89449a96287aedfce1805293": { + hash: "0x452dd11bf2cac23a2c366d0adae6cc6451a3665c89449a96287aedfce1805293", + type: 2, + from: "0x08eec580ad41e9994599bad7d2a74a9874a2852c", + to: null, // Contract creation has no 'to' address + value: "0x0", + blockNumber: 24053298, + gas: "801,386", + gasUsed: "794,942", + maxFeePerGas: "2.181898787 Gwei", + maxPriorityFeePerGas: "2,000,000,000 wei", + effectiveGasPrice: "2.126998446 Gwei", + nonce: 680, + status: "success" as const, + contractAddress: "0xd5bd7a2d6e13d351e157e2a0396dac810d6af390", + hasInputData: true, // Contains contract bytecode + }, + }, + + addresses: { + // Vitalik's address - well known EOA with ENS + vitalik: { + address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + type: "contract" as const, // Now a contract after EIP-4337 + hasENS: true, + ensName: "vitalik.eth", + }, + // USDC contract - ERC20 token + usdc: { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // Uniswap V2 Router - verified contract + uniswapRouter: { + address: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + type: "contract" as const, + name: "Uniswap V2: Router 2", + }, + // Bored Ape Yacht Club - ERC721 NFT collection + bayc: { + address: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", + type: "erc721" as const, + name: "BoredApeYachtClub", + fullName: "Bored Ape Yacht Club", + symbol: "BAYC", + // NFT Collection Details + totalMinted: "10,000 NFTs", + // Contract Details + verified: true, + verifiedAt: "8/8/2024", + matchType: "Partial Match", + compiler: "0.7.0+commit.9e61f92b", + // Read Functions (21 total) + readFunctions: [ + "BAYC_PROVENANCE", + "MAX_APES", + "REVEAL_TIMESTAMP", + "apePrice", + "balanceOf", + "baseURI", + "getApproved", + "isApprovedForAll", + "maxApePurchase", + "name", + "owner", + "ownerOf", + "saleIsActive", + "startingIndex", + "startingIndexBlock", + "supportsInterface", + "symbol", + "tokenByIndex", + "tokenOfOwnerByIndex", + "tokenURI", + "totalSupply", + ], + // Write Functions (16 total) + writeFunctions: [ + "approve", + "emergencySetStartingIndexBlock", + "flipSaleState", + "mintApe", + "renounceOwnership", + "reserveApes", + "safeTransferFrom", + "setApprovalForAll", + "setBaseURI", + "setProvenanceHash", + "setRevealTimestamp", + "setStartingIndex", + "transferFrom", + "transferOwnership", + "withdraw", + ], + // Events (4 total) + events: ["Approval", "ApprovalForAll", "OwnershipTransferred", "Transfer"], + }, + // Rarible - ERC1155 multi-token collection + rarible: { + address: "0xd07dc4262BCDbf85190C01c996b4C06a461d2430", + type: "erc1155" as const, + name: "Rarible", + symbol: "RARI", + // Multi-Token Collection Details + metadataUri: "ipfs:/", + // Contract Details + verified: true, + verifiedAt: "8/8/2024", + matchType: "MATCH", + compiler: "0.5.17+commit.d19bba13", + // Read Functions (16 total) + readFunctions: [ + "balanceOf", + "balanceOfBatch", + "contractURI", + "creators", + "fees", + "getFeeBps", + "getFeeRecipients", + "isApprovedForAll", + "isOwner", + "isSigner", + "name", + "owner", + "supportsInterface", + "symbol", + "tokenURIPrefix", + "uri", + ], + // Write Functions (12 total) + writeFunctions: [ + "addSigner", + "burn", + "mint", + "removeSigner", + "renounceOwnership", + "renounceSigner", + "safeBatchTransferFrom", + "safeTransferFrom", + "setApprovalForAll", + "setContractURI", + "setTokenURIPrefix", + "transferOwnership", + ], + // Events (8 total) + events: [ + "ApprovalForAll", + "OwnershipTransferred", + "SecondarySaleFees", + "SignerAdded", + "SignerRemoved", + "TransferBatch", + "TransferSingle", + "URI", + ], + }, + }, + + // Specific NFT tokens for token detail page tests + tokens: { + // BAYC #1 - First Bored Ape + baycToken1: { + contractAddress: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", + tokenId: "1", + standard: "erc721" as const, + collectionName: "BoredApeYachtClub", + collectionSymbol: "BAYC", + collectionSize: "10,000 NFTs", + tokenUri: "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/1", + // Properties (5 total for token #1) + properties: [ + { trait: "MOUTH", value: "Grin" }, + { trait: "CLOTHES", value: "Vietnam Jacket" }, + { trait: "BACKGROUND", value: "Orange" }, + { trait: "EYES", value: "Blue Beams" }, + { trait: "FUR", value: "Robot" }, + ], + }, + // BAYC #100 - Another well-known Bored Ape + baycToken100: { + contractAddress: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", + tokenId: "100", + standard: "erc721" as const, + collectionName: "BoredApeYachtClub", + collectionSymbol: "BAYC", + collectionSize: "10,000 NFTs", + tokenUri: "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/100", + // Properties (5 total for token #100) + properties: [ + { trait: "BACKGROUND", value: "Yellow" }, + { trait: "MOUTH", value: "Bored Cigarette" }, + { trait: "HAT", value: "Party Hat 2" }, + { trait: "FUR", value: "Dark Brown" }, + { trait: "EYES", value: "Wide Eyed" }, + ], + }, + // Rarible token - ERC1155 + raribleToken: { + contractAddress: "0xd07dc4262BCDbf85190C01c996b4C06a461d2430", + tokenId: "1", + standard: "erc1155" as const, + collectionName: "Rarible", + collectionSymbol: "RARI", + }, + }, +}; diff --git a/e2e/fixtures/optimism.ts b/e2e/fixtures/optimism.ts new file mode 100644 index 0000000..abb935c --- /dev/null +++ b/e2e/fixtures/optimism.ts @@ -0,0 +1,362 @@ +// cspell:ignore velodrome bedrock ecotone fjord holocene isthmus sequencer +export const OPTIMISM = { + chainId: "10", + + // Optimism mainnet regenesis: November 11, 2021 (current chain) + // Bedrock upgrade: June 6, 2023 (block ~105,235,063) + // Block time: 2 seconds + // Upgrades follow Superchain-wide activation timestamps + + blocks: { + // Genesis block - Optimism mainnet (post-regenesis) + // Note: Contains 8,893 transactions from state migration + "0": { + number: 0, + txCount: 8893, + gasUsed: "0", + gasLimit: "15,000,000", + hash: "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 100,000,000 (May 20, 2023) + // Pre-Bedrock block + "100000000": { + number: 100000000, + txCount: 1, + gasUsed: "108,922", + gasUsedPercent: "0.7%", + gasLimit: "15,000,000", + hash: "0x9bb1d3a1d3deb6f1298011d4bebf994f3ed7d02ae64a491568f78c4ed5124cb5", + parentHash: "0x9f423a470be5e8c2bcd3c046dd67ceb9f184af92fea432a4016da68240510ce9", + }, + // Block 110,000,000 (September 24, 2023) + // Post-Bedrock, pre-Ecotone + "110000000": { + number: 110000000, + txCount: 4, + gasUsed: "301,587", + gasUsedPercent: "1.0%", + gasLimit: "30,000,000", + baseFeePerGas: "0.063 Gwei", + hash: "0x429308982bd568afa89a04ccbbc2f07b9058176e19fd6622fd87e346ef07ed23", + parentHash: "0xdd6583a74ad3d1c383d9f657268d085f6f39d11a53daf0e5fd873dfeb09f0ac7", + feeRecipientPartial: "0x42000000", + }, + // Block 120,000,000 (May 13, 2024) + // Post-Ecotone (EIP-4844 blob support) + "120000000": { + number: 120000000, + txCount: 21, + gasUsed: "27,554,949", + gasUsedPercent: "91.9%", + gasLimit: "30,000,000", + baseFeePerGas: "0.06 Gwei", + hash: "0xcad6bf99757384f6f16fac0974aa234bb9bc866d8c89d9a56addbd9a3f13dc13", + parentHash: "0x8bce6c507e43379bef7817dbf57b9b0181e744a6c5f841d5ee57b4e09f6b09df", + feeRecipientPartial: "0x42000000", + }, + // Block 130,000,000 (December 30, 2024) + // Post-Holocene + "130000000": { + number: 130000000, + txCount: 13, + gasUsed: "1,535,948", + gasUsedPercent: "2.6%", + gasLimit: "60,000,000", + baseFeePerGas: "0.00000026 Gwei", + hash: "0xaf131f54209291613f0b74e61903405ea84bf30368ea5c6cf787992351ad843d", + parentHash: "0x547a69b3a63b80cee045cdcb0759bad305f42aa30dbf6c4fa8fbe70d44e37e65", + feeRecipientPartial: "0x42000000", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // Velodrome Finance swap - Legacy transaction + "0xa8d73ea0639f39157f787a29591b36fc73c19b443bbe8416d8d6f24858063910": { + hash: "0xa8d73ea0639f39157f787a29591b36fc73c19b443bbe8416d8d6f24858063910", + type: 0, // Legacy transaction + from: "0x1dE686d54FA9b786870c3b67e1430BD1F08C1c5F", + to: "0x9c12939390052919aF3155f41Bf4160Fd3666A6f", // Velodrome Router + value: "0x0", + blockNumber: 84855387, + gas: "331,424", + gasUsed: "249,652", + gasPrice: "0.001 Gwei", + nonce: 89, + position: 0, + status: "success" as const, + hasInputData: true, + method: "swapExactTokensForTokens", + // L2 fee breakdown (Optimism specific) + l2Fee: "0.000000249652 ETH", + l1Fee: "0.000229678666963588 ETH", + txFee: "0.000229928318963588 ETH", + }, + + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) + // ============================================ + + // OP token transfer (EIP-1559) + "0xdcf7c4afb479cd47f7ce263cbbb298f559b81fc592cc07737935a6166fb90f0c": { + hash: "0xdcf7c4afb479cd47f7ce263cbbb298f559b81fc592cc07737935a6166fb90f0c", + type: 2, // EIP-1559 + from: "0x29185eB8cfD22Aa719529217bFbadE61677e0Ad2", + to: "0x4200000000000000000000000000000000000042", // OP Token + value: "0x0", + blockNumber: 120011272, + gas: "40,358", + gasUsed: "39,988", + maxFeePerGas: "0.191 Gwei", + maxPriorityFeePerGas: "0.004 Gwei", + effectiveGasPrice: "0.066 Gwei", + nonce: 190, + position: 11, + status: "success" as const, + hasInputData: true, + method: "transfer", + txFee: "0.000002714211193107 ETH", + }, + + // OP token delegate (EIP-1559) + "0x36a239e68d43afbb742a66e2c5456f443e20e2bf79812f8ee80d2c444bcb5d89": { + hash: "0x36a239e68d43afbb742a66e2c5456f443e20e2bf79812f8ee80d2c444bcb5d89", + type: 2, // EIP-1559 + from: "0x7192744441e4C9845408b5928b80cA5dd40C41bF", + to: "0x4200000000000000000000000000000000000042", // OP Token + value: "0x0", + blockNumber: 129267009, + gas: "99,799", + gasUsed: "98,824", + maxFeePerGas: "0.000108 Gwei", + maxPriorityFeePerGas: "0.0001 Gwei", + nonce: 161, + status: "success" as const, + hasInputData: true, + method: "delegate", + }, + + // ============================================ + // SYSTEM TRANSACTIONS (Type 126) - OP Stack specific + // ============================================ + + // L2 Cross Domain Messenger relay - System transaction (Type 126) + "0x5d3522dad0d0745b59e9443733f8423548f99856c00768aba9779ae288dedd0a": { + hash: "0x5d3522dad0d0745b59e9443733f8423548f99856c00768aba9779ae288dedd0a", + type: 126, // Deposit transaction type + from: "0x36BDE71C97B33Cc4729cf772aE268934f7AB70B2", // Aliased L1 Cross-Domain Messenger + to: "0x4200000000000000000000000000000000000007", // L2CrossDomainMessenger + value: "0x0", + blockNumber: 106744423, + gas: "387,675", + gasUsed: "96,955", + nonce: 370362, + status: "success" as const, + hasInputData: true, + method: "relayMessage", + // System transactions have zero fees + txFee: "0 ETH", + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // Native USDC on Optimism - Circle's native USDC + usdc: { + address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // Bridged USDC.e (legacy) + usdce: { + address: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", + type: "erc20" as const, + symbol: "USDC.e", + name: "Bridged USDC", + decimals: 6, + }, + // WETH on Optimism (predeploy) + weth: { + address: "0x4200000000000000000000000000000000000006", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // OP token - Optimism governance token + op: { + address: "0x4200000000000000000000000000000000000042", + type: "erc20" as const, + symbol: "OP", + name: "Optimism", + decimals: 18, + }, + // USDT on Optimism + usdt: { + address: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", + type: "erc20" as const, + symbol: "USDT", + name: "Tether USD", + decimals: 6, + }, + // DAI on Optimism + dai: { + address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", + type: "erc20" as const, + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // Velodrome Finance Router - Main DEX on Optimism + velodromeRouter: { + address: "0x9c12939390052919aF3155f41Bf4160Fd3666A6f", + type: "contract" as const, + name: "Velodrome Finance: Router", + }, + // Velodrome Universal Router (V2) + velodromeUniversalRouter: { + address: "0x01D40099fCD87C018969B0e8D4aB1633Fb34763C", + type: "contract" as const, + name: "Velodrome: Universal Router", + }, + // Uniswap V3 Router + uniswapV3Router: { + address: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + type: "contract" as const, + name: "Uniswap V3: Swap Router", + }, + // Uniswap Universal Router + uniswapUniversalRouter: { + address: "0xCb1355ff08Ab38bBCE60111F1bb2B784bE25D7e8", + type: "contract" as const, + name: "Uniswap: Universal Router", + }, + + // ============================================ + // BRIDGE CONTRACTS + // ============================================ + + // Optimism Portal (L1 to L2 deposits) + optimismPortal: { + address: "0xbEb5Fc579115071764c7423A4f12eDde41f106Ed", + type: "contract" as const, + name: "Optimism: Portal", + }, + + // ============================================ + // SYSTEM CONTRACTS (OP Stack predeploys) + // ============================================ + + // SequencerFeeVault - Receives transaction fees (fee recipient) + sequencerFeeVault: { + address: "0x4200000000000000000000000000000000000011", + type: "contract" as const, + name: "SequencerFeeVault", + }, + // L2CrossDomainMessenger - Bridge messaging + l2CrossDomainMessenger: { + address: "0x4200000000000000000000000000000000000007", + type: "contract" as const, + name: "L2CrossDomainMessenger", + }, + // L2StandardBridge - Token bridging + l2StandardBridge: { + address: "0x4200000000000000000000000000000000000010", + type: "contract" as const, + name: "L2StandardBridge", + }, + // GasPriceOracle - L1 fee calculation + gasPriceOracle: { + address: "0x420000000000000000000000000000000000000F", + type: "contract" as const, + name: "GasPriceOracle", + }, + // L1Block - L1 block attributes + l1Block: { + address: "0x4200000000000000000000000000000000000015", + type: "contract" as const, + name: "L1Block", + }, + // L2ToL1MessagePasser - Withdrawals + l2ToL1MessagePasser: { + address: "0x4200000000000000000000000000000000000016", + type: "contract" as const, + name: "L2ToL1MessagePasser", + }, + // BaseFeeVault - Collects base fees + baseFeeVault: { + address: "0x4200000000000000000000000000000000000019", + type: "contract" as const, + name: "BaseFeeVault", + }, + // L1FeeVault - Collects L1 data fees + l1FeeVault: { + address: "0x420000000000000000000000000000000000001A", + type: "contract" as const, + name: "L1FeeVault", + }, + }, + + // Upgrade timestamps (Unix) for reference + upgrades: { + bedrock: { + timestamp: 1686079703, + date: "2023-06-06T16:28:23Z", + description: "Major architecture upgrade - modular rollup design", + }, + canyon: { + timestamp: 1704992401, + date: "2024-01-11T17:00:01Z", + description: "Shapella support, EIP-4788 beacon root", + }, + delta: { + timestamp: 1708560000, + date: "2024-02-22T00:00:00Z", + description: "Span batches for data compression", + }, + ecotone: { + timestamp: 1710374401, + date: "2024-03-14T00:00:01Z", + description: "EIP-4844 blob support, ~90% fee reduction", + }, + fjord: { + timestamp: 1720627201, + date: "2024-07-10T16:00:01Z", + description: "Brotli compression, RIP-7212 secp256r1 precompile", + }, + granite: { + timestamp: 1726070401, + date: "2024-09-11T16:00:01Z", + description: "Permissionless fault proofs re-enabled", + }, + holocene: { + timestamp: 1736445601, + date: "2025-01-09T18:00:01Z", + description: "Stricter derivation, EIP-1559 configurability", + }, + isthmus: { + timestamp: 1746806401, + date: "2025-05-09T16:00:01Z", + description: "Pectra L2 support", + }, + jovian: { + timestamp: 1764691201, + date: "2025-12-02T16:00:01Z", + description: "Future upgrade (pending)", + }, + }, +}; diff --git a/e2e/fixtures/polygon.ts b/e2e/fixtures/polygon.ts new file mode 100644 index 0000000..98dfd2e --- /dev/null +++ b/e2e/fixtures/polygon.ts @@ -0,0 +1,357 @@ +// cspell:ignore quickswap uniswap aavegotchi opensea matic heimdall +export const POLYGON = { + chainId: "137", + + // Polygon PoS mainnet launch: May 30, 2020 + // Originally Matic Network, rebranded to Polygon in February 2021 + // Block time: ~2 seconds + // Consensus: Proof-of-Stake with Heimdall (consensus) + Bor (block production) + // Native token: MATIC (migrated to POL in September 2024) + + blocks: { + // Genesis block - Polygon PoS mainnet launch (May 30, 2020) + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "10,000,000", + hash: "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 10,000,000 (January 2021) + // Early Polygon activity + "10000000": { + number: 10000000, + txCount: 5, + gasUsed: "4,278,607", + gasUsedPercent: "21.4%", + gasLimit: "20,000,000", + hash: "0x57d179b4ed2379580c46d9809c8918e28c4f1debea8e15013749694f37c14105", + parentHash: "0xee82cb38f164bfdb75092bcc5c1cb302385a48b2d9abd647af934ecb89db61f4", + }, + // Block 20,000,000 (October 2021) + // Growing DeFi activity + "20000000": { + number: 20000000, + txCount: 110, + gasUsed: "19,998,514", + gasUsedPercent: "100.0%", + gasLimit: "20,000,000", + hash: "0x8b047896ef57b3ebe10a6b0e4cc28a1e0491b09706c6e0f2b5eff3992cf04730", + parentHash: "0x3a61eaf4cc52b8c3a217fbcfd0e862b59e01d504368b659db9314921879dcf9e", + }, + // Block 30,000,000 (July 2022) + // Mature network, increased gas limit + "30000000": { + number: 30000000, + txCount: 133, + gasUsed: "18,658,580", + gasUsedPercent: "62.2%", + gasLimit: "30,000,000", + hash: "0xd409f8e3d2db0568634bcedc71ae48f4ed8dfcececb723db218a0fc37a5e8d44", + parentHash: "0xa9d361ef8ba2d3c4d05ea5bee19026e9cad27ae1e95cf9bc86ec30e246aba9f4", + }, + // Block 38,189,056 (January 17, 2023) + // Delhi Hard Fork - reduced sprint length, smoothed baseFee + "38189056": { + number: 38189056, + txCount: 121, + gasUsed: "19,246,742", + gasUsedPercent: "68.2%", + gasLimit: "28,207,416", + hash: "0x19bcd5f19d3f928aae2b582b0e005c963a8ef0bfd005d1b639a5b0b5dbd632d6", + parentHash: "0x9c0a52241e01b66eb8987f894dea8b22d4ba1e2261217ddbaf4638c8699df79e", + }, + // Block 50,000,000 (November 2023) + // Post-Delhi, high activity + "50000000": { + number: 50000000, + txCount: 564, + gasUsed: "26,134,824", + gasUsedPercent: "88.4%", + gasLimit: "29,563,532", + hash: "0xed6bc55bb3fbf391fb47a96bb0327906a2dab5f50c1330f89684b79a5195efaa", + parentHash: "0xafe719f6ca102ab7b7b9fd367688e73a777d02d21d6a692a8ff6a6eb3c2f7c27", + }, + // Block 62,278,656 (September 25, 2024) + // Ahmedabad Hard Fork - MATIC to POL migration + "62278656": { + number: 62278656, + txCount: 65, + gasUsed: "7,292,621", + gasUsedPercent: "24.0%", + gasLimit: "30,442,418", + hash: "0xc207d13429c37fded959648b9f6d5d51d4cb65371c9b2f3a40f93f750cf000c4", + parentHash: "0xbfe87634499bbba796ad1b7d064c97354a12ea43c3f2f8f0a91a3d9987ef883e", + }, + // Block 65,000,000 (December 2024) + // Post-Ahmedabad, POL era + "65000000": { + number: 65000000, + txCount: 103, + gasUsed: "13,884,738", + gasUsedPercent: "46.3%", + gasLimit: "30,000,000", + hash: "0x40a243f82db77b7557e7b56808e93ef72c5bc83f16ad5ede236b496a78736eb6", + parentHash: "0x5fa5e769a0b2bd46a7f857af0507587d2d02f67305d917dfdeeb2d6087b21a18", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // OpenSea NFT transfer from block 30,000,000 - Legacy transaction + "0xb14598e46791c2f0ab366ba2fd4a533e21a0c9894f902773e02e3869b7373c3e": { + hash: "0xb14598e46791c2f0ab366ba2fd4a533e21a0c9894f902773e02e3869b7373c3e", + type: 0, // Legacy transaction + from: "0x3ce07ad298ee2b3aabea8c8b3f496c3acc51e647", + to: "0x2953399124f0cbb46d2cbacd8a89cf0599974963", // OpenSea Storefront + value: "0x0", + blockNumber: 30000000, + gas: "189,792", + gasUsed: "89,556", + gasPrice: "125 Gwei", + nonce: 30554656, + position: 0, + status: "success" as const, + hasInputData: true, + }, + + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) + // ============================================ + + // Failed DeFi swap from block 50,000,000 - EIP-1559 transaction + "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0": { + hash: "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0", + type: 2, // EIP-1559 + from: "0x2c61d22af7b615d7d41def680b6edac29076709d", + to: "0x826a4f4da02588737d3c27325b14f39b5151ca3c", + value: "0x0", + blockNumber: 50000000, + gas: "1,000,014", + gasUsed: "66,787", + nonce: 2151, + position: 0, + status: "failed" as const, // Failed: ERC20 transfer amount exceeds balance + hasInputData: true, + }, + + // Contract interaction from block 65,000,000 - EIP-1559 transaction + "0x65edbf03a20a0317295efaeb9c20836b20b16740c8311ce51ceee91d7674b20d": { + hash: "0x65edbf03a20a0317295efaeb9c20836b20b16740c8311ce51ceee91d7674b20d", + type: 2, // EIP-1559 + from: "0x706c7fa886ccaf510e570c5fa91f5988b15a8a56", + to: "0xe957a692c97566efc85f995162fa404091232b2e", + value: "0x0", + blockNumber: 65000000, + gas: "234,624", + gasUsed: "225,106", + nonce: 34547, + position: 0, + status: "success" as const, + hasInputData: true, + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // Wrapped POL (WPOL) - formerly WMATIC + wpol: { + address: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + type: "erc20" as const, + symbol: "WPOL", + name: "Wrapped POL", + decimals: 18, + }, + // USDC (Bridged from Ethereum) + usdc: { + address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + type: "erc20" as const, + symbol: "USDC.e", + name: "USD Coin (Bridged)", + decimals: 6, + }, + // Native USDC on Polygon + usdcNative: { + address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // USDT on Polygon + usdt: { + address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + type: "erc20" as const, + symbol: "USDT", + name: "Tether USD", + decimals: 6, + }, + // WETH on Polygon + weth: { + address: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // DAI on Polygon + dai: { + address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + type: "erc20" as const, + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + }, + // AAVE on Polygon + aave: { + address: "0xD6DF932A45C0f255f85145f286eA0b292B21C90B", + type: "erc20" as const, + symbol: "AAVE", + name: "Aave", + decimals: 18, + }, + // LINK on Polygon + link: { + address: "0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39", + type: "erc20" as const, + symbol: "LINK", + name: "ChainLink Token", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // QuickSwap Router - Main DEX on Polygon + quickswapRouter: { + address: "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff", + type: "contract" as const, + name: "QuickSwap: Router", + }, + // Uniswap V3 Router on Polygon + uniswapV3Router: { + address: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + type: "contract" as const, + name: "Uniswap V3: SwapRouter", + }, + // SushiSwap Router + sushiswapRouter: { + address: "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", + type: "contract" as const, + name: "SushiSwap: Router", + }, + + // ============================================ + // NFT CONTRACTS + // ============================================ + + // OpenSea Storefront (Polygon) + openseaStorefront: { + address: "0x2953399124F0cBB46d2CbACD8A89cF0599974963", + type: "contract" as const, + name: "OpenSea Shared Storefront", + }, + + // ============================================ + // LENDING PROTOCOLS + // ============================================ + + // Aave V3 Pool on Polygon + aaveV3Pool: { + address: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", + type: "contract" as const, + name: "Aave V3: Pool", + }, + + // ============================================ + // BRIDGE CONTRACTS + // ============================================ + + // Polygon PoS Bridge (RootChainManager on Ethereum side) + polygonBridge: { + address: "0xA0c68C638235ee32657e8f720a23ceC1bFc77C77", + type: "contract" as const, + name: "Polygon: ERC20 Bridge", + }, + + // ============================================ + // SYSTEM CONTRACTS + // ============================================ + + // Child Chain Manager - manages token deposits + childChainManager: { + address: "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa", + type: "contract" as const, + name: "Polygon: ChildChainManager", + }, + // State Receiver - receives state from Ethereum + stateReceiver: { + address: "0x0000000000000000000000000000000000001001", + type: "contract" as const, + name: "Polygon: StateReceiver", + }, + // Matic Token (native, system address) + maticToken: { + address: "0x0000000000000000000000000000000000001010", + type: "contract" as const, + name: "Polygon: POL Token", + }, + }, + + // Upgrade timestamps and block heights for reference + upgrades: { + mainnetLaunch: { + blockHeight: 0, + timestamp: 1590824836, + date: "2020-05-30T07:47:16Z", + description: "Polygon PoS mainnet launch (as Matic Network)", + }, + rebranding: { + timestamp: 1613001600, + date: "2021-02-11T00:00:00Z", + description: "Matic Network rebrands to Polygon", + }, + eip1559: { + blockHeight: 23850000, + timestamp: 1647619200, + date: "2022-03-18T08:00:00Z", + description: "EIP-1559 and London hard fork activation", + }, + delhi: { + blockHeight: 38189056, + timestamp: 1673974800, + date: "2023-01-17T18:00:00Z", + description: "Delhi hard fork - reduced sprint length (64→16), smoothed baseFee", + }, + napoli: { + timestamp: 1710892800, + date: "2024-03-20T00:00:00Z", + description: "Napoli hard fork - RIP-7212 secp256r1 precompile support", + }, + ahmedabad: { + blockHeight: 62278656, + timestamp: 1727344800, + date: "2024-09-26T09:00:00Z", + description: "Ahmedabad hard fork - MATIC to POL migration, code size limit increase", + }, + heimdallV2: { + heimdallHeight: 24404500, + timestamp: 1752159600, + date: "2025-07-10T14:00:00Z", + description: "Heimdall v2 - consensus layer upgrade to CometBFT, ~5s finality", + }, + madhugiri: { + timestamp: 1763920800, + date: "2025-12-09T00:00:00Z", + description: "Madhugiri hard fork - 33% throughput increase, 1s consensus time", + }, + }, +}; diff --git a/e2e/fixtures/test.ts b/e2e/fixtures/test.ts new file mode 100644 index 0000000..65b2278 --- /dev/null +++ b/e2e/fixtures/test.ts @@ -0,0 +1,22 @@ +import { test as base } from "@playwright/test"; + +/** + * Custom test fixture that increases timeout on retries. + * Base timeout: 60 seconds + * Formula: baseTimeout + (20 seconds * retryCount) + * - Retry 0: 60s + * - Retry 1: 80s + * - Retry 2: 100s + * - Retry 3: 120s + */ +export const test = base.extend({}); + +const BASE_TIMEOUT = 60000; +const TIMEOUT_INCREMENT = 20000; // 20 seconds per retry + +test.beforeEach(async ({}, testInfo) => { + const newTimeout = BASE_TIMEOUT + TIMEOUT_INCREMENT * testInfo.retry; + testInfo.setTimeout(newTimeout); +}); + +export { expect } from "@playwright/test"; diff --git a/e2e/helpers/wait.ts b/e2e/helpers/wait.ts new file mode 100644 index 0000000..2e41859 --- /dev/null +++ b/e2e/helpers/wait.ts @@ -0,0 +1,129 @@ +import type { Page, TestInfo } from "@playwright/test"; +import { expect } from "@playwright/test"; + +/** + * Default timeout for assertions in e2e tests. + * - Local: 5000ms (5 seconds) + * - CI (GitHub Actions): 10000ms (10 seconds) + */ +export const DEFAULT_TIMEOUT = process.env.CI ? 10000 : 5000; + +/** + * Shared wait helpers for e2e tests with retry-aware timeouts. + * + * Timeout formula: baseTimeout + (increment * retryCount) + * - Retry 0: 30s + * - Retry 1: 40s + * - Retry 2: 50s + * - Retry 3: 60s + * + * This ensures flaky tests have more time on retries while keeping + * initial runs fast. + */ + +const BASE_TIMEOUT = 30000; // 30 seconds base +const TIMEOUT_INCREMENT = 10000; // 10 seconds per retry + +/** + * Calculate timeout based on retry count + */ +function getTimeout(testInfo: TestInfo): number { + return BASE_TIMEOUT + TIMEOUT_INCREMENT * testInfo.retry; +} + +/** + * Wait for block page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForBlockContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator("text=Transactions:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} + +/** + * Wait for transaction page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForTxContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator("text=Transaction Hash:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} + +/** + * Wait for address page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForAddressContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator("text=Balance:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} + +/** + * Wait for token page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForTokenContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator(".erc721-header") + .or(page.locator(".erc1155-header")) + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} diff --git a/e2e/pages/address.page.ts b/e2e/pages/address.page.ts new file mode 100644 index 0000000..6a7e6c0 --- /dev/null +++ b/e2e/pages/address.page.ts @@ -0,0 +1,69 @@ +import type { Locator, Page } from "@playwright/test"; +import { DEFAULT_TIMEOUT } from "../helpers/wait"; + +export class AddressPage { + readonly page: Page; + readonly container: Locator; + readonly addressType: Locator; + readonly ensName: Locator; + readonly balance: Locator; + readonly txDetails: Locator; + readonly txHistory: Locator; + readonly loader: Locator; + readonly errorText: Locator; + readonly contractBytecode: Locator; + + constructor(page: Page) { + this.page = page; + this.container = page.locator(".container-wide"); + this.addressType = page.locator(".address-type-label"); + this.ensName = page.locator(".address-ens-name"); + this.balance = page.locator(".tx-value-highlight"); + this.txDetails = page.locator(".tx-details"); + this.txHistory = page.locator(".recent-transactions-table"); + this.loader = page.locator(".loader-container"); + this.errorText = page.locator(".text-error, .error-text"); + this.contractBytecode = page.locator(".contract-bytecode, .bytecode-container"); + } + + async goto(address: string, chainId = "1") { + await this.page.goto(`/${chainId}/address/${address}`); + } + + async waitForLoad() { + await this.loader.waitFor({ state: "hidden", timeout: DEFAULT_TIMEOUT * 3 }); + } + + async getAddressType(): Promise { + return (await this.addressType.textContent()) ?? ""; + } + + async getBalance(): Promise { + return (await this.balance.textContent()) ?? ""; + } + + async hasENSName(): Promise { + return this.ensName.isVisible(); + } + + async getENSName(): Promise { + if (await this.ensName.isVisible()) { + return (await this.ensName.textContent()) ?? ""; + } + return ""; + } + + async getRowValue(label: string): Promise { + const row = this.txDetails.locator(".tx-row", { hasText: label }); + return (await row.locator(".tx-value").textContent()) ?? ""; + } + + async getTransactionCount(): Promise { + return this.getRowValue("Transactions"); + } + + async isContract(): Promise { + const type = await this.getAddressType(); + return type.toLowerCase().includes("contract") || type.toLowerCase().includes("erc"); + } +} diff --git a/e2e/pages/block.page.ts b/e2e/pages/block.page.ts new file mode 100644 index 0000000..76ba26a --- /dev/null +++ b/e2e/pages/block.page.ts @@ -0,0 +1,59 @@ +import type { Locator, Page } from "@playwright/test"; +import { DEFAULT_TIMEOUT } from "../helpers/wait"; + +export class BlockPage { + readonly page: Page; + readonly container: Locator; + readonly blockNumber: Locator; + readonly blockLabel: Locator; + readonly timestampAge: Locator; + readonly statusBadge: Locator; + readonly txDetails: Locator; + readonly loader: Locator; + readonly errorText: Locator; + readonly navPrevBtn: Locator; + readonly navNextBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.container = page.locator(".container-wide"); + this.blockNumber = page.locator(".block-number"); + this.blockLabel = page.locator(".block-label"); + this.timestampAge = page.locator(".block-timestamp-age"); + this.statusBadge = page.locator(".block-status-badge"); + this.txDetails = page.locator(".tx-details"); + this.loader = page.locator(".loader-container"); + this.errorText = page.locator(".text-error, .error-text"); + this.navPrevBtn = page.locator(".block-nav-btn").first(); + this.navNextBtn = page.locator(".block-nav-btn").last(); + } + + async goto(blockNumber: number | string, chainId = "1") { + await this.page.goto(`/${chainId}/block/${blockNumber}`); + } + + async waitForLoad() { + await this.loader.waitFor({ state: "hidden", timeout: DEFAULT_TIMEOUT * 3 }); + } + + async getBlockNumber(): Promise { + return (await this.blockNumber.textContent()) ?? ""; + } + + async getRowValue(label: string): Promise { + const row = this.txDetails.locator(".tx-row", { hasText: label }); + return (await row.locator(".tx-value").textContent()) ?? ""; + } + + async getTransactionCount(): Promise { + return this.getRowValue("Transactions"); + } + + async getGasUsed(): Promise { + return this.getRowValue("Gas Used"); + } + + async getFeeRecipient(): Promise { + return this.getRowValue("Fee Recipient"); + } +} diff --git a/e2e/pages/transaction.page.ts b/e2e/pages/transaction.page.ts new file mode 100644 index 0000000..812bb11 --- /dev/null +++ b/e2e/pages/transaction.page.ts @@ -0,0 +1,66 @@ +import type { Locator, Page } from "@playwright/test"; +import { DEFAULT_TIMEOUT } from "../helpers/wait"; + +export class TransactionPage { + readonly page: Page; + readonly container: Locator; + readonly txHash: Locator; + readonly txLabel: Locator; + readonly statusBadge: Locator; + readonly txDetails: Locator; + readonly loader: Locator; + readonly errorText: Locator; + + constructor(page: Page) { + this.page = page; + this.container = page.locator(".container-wide"); + this.txHash = page.locator(".header-subtitle"); + this.txLabel = page.locator(".block-label"); + this.statusBadge = page.locator('[class*="status-badge"]'); + this.txDetails = page.locator(".tx-details"); + this.loader = page.locator(".loader-container"); + this.errorText = page.locator(".text-error, .error-text"); + } + + async goto(txHash: string, chainId = "1") { + await this.page.goto(`/${chainId}/tx/${txHash}`); + } + + async waitForLoad() { + await this.loader.waitFor({ state: "hidden", timeout: DEFAULT_TIMEOUT * 3 }); + } + + async getStatus(): Promise<"success" | "failed"> { + const badge = await this.statusBadge.getAttribute("class"); + return badge?.includes("success") ? "success" : "failed"; + } + + async getRowValue(label: string): Promise { + const row = this.txDetails.locator(".tx-row", { hasText: label }); + return (await row.locator(".tx-value").textContent()) ?? ""; + } + + async getFromAddress(): Promise { + return this.getRowValue("From"); + } + + async getToAddress(): Promise { + return this.getRowValue("To"); + } + + async getValue(): Promise { + return this.getRowValue("Value"); + } + + async getGas(): Promise { + return this.getRowValue("Gas"); + } + + async getBlockNumber(): Promise { + return this.getRowValue("Block"); + } + + async getGasUsed(): Promise { + return this.getRowValue("Gas Used"); + } +} diff --git a/e2e/tests/arbitrum.spec.ts b/e2e/tests/arbitrum.spec.ts new file mode 100644 index 0000000..a93eb4c --- /dev/null +++ b/e2e/tests/arbitrum.spec.ts @@ -0,0 +1,536 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { AddressPage } from "../pages/address.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { ARBITRUM } from "../fixtures/arbitrum"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; + +const CHAIN_ID = ARBITRUM.chainId; + +// Transaction hash constants for readability +const UNISWAP_SWAP = "0x87815a816c02b5a563a026e4a37d423734204b50972e75284b62f05e4134ae44"; +const USDC_TRANSFER = "0x160687cbf03f348cf36997dbab53abbd32d91af5971bccac4cfa1577da27607e"; + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Arbitrum One - Block Page", () => { + test("genesis block #0 - Arbitrum One launch", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + + // Genesis block should have 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + } + }); + + test("block #22,207,817 - Nitro upgrade block", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["22207817"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Nitro upgrade block - 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + } + }); + + test("block #100,000,000 - post-Nitro with gas details", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient (sequencer address) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #100,000,000 more details section shows correct hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #200,000,000 - post-ArbOS 20 Atlas (Dencun)", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["200000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #200,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["200000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #300,000,000 - post-ArbOS 32 Bianca (Stylus)", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["300000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("genesis block more details section shows correct hash", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + } + } + }); + + test("block navigation buttons work on Arbitrum", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(ARBITRUM.blocks["100000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Arbitrum One - Transaction Page", () => { + test("displays Uniswap V3 swap with all details", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + await expect(page.locator("text=Value:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + } + }); + + test("shows correct from and to addresses for swap", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction value and fee", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify value contains ETH + const value = await txPage.getValue(); + expect(value).toContain("ETH"); + + // Verify transaction fee is displayed + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays USDC transfer transaction (EIP-1559)", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[USDC_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be USDC contract + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction with input data", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Contract interaction should have input data + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays other attributes section with nonce", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + } + }); + + test("displays block number link", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("USDC transfer shows correct addresses", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[USDC_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// ADDRESS TESTS +// ============================================ + +test.describe("Arbitrum One - Address Page", () => { + test("displays native USDC contract details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays bridged USDC.e contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.usdce; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays WETH contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.weth; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays ARB governance token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.arb; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GMX token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.gmx; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Uniswap V3 Router contract with details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.uniswapV3Router; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays Uniswap Universal Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.uniswapUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GMX Vault contract with details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.gmxVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays GMX Position Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.gmxPositionRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays ArbSys system precompile", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.arbSys; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); diff --git a/e2e/tests/base.spec.ts b/e2e/tests/base.spec.ts new file mode 100644 index 0000000..ac0a69e --- /dev/null +++ b/e2e/tests/base.spec.ts @@ -0,0 +1,437 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { AddressPage } from "../pages/address.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { BASE } from "../fixtures/base"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; + +const CHAIN_ID = BASE.chainId; + +// Transaction hash constants for readability +const AERODROME_SWAP = "0x961cf2c57f006d8c6fdbe266b2ef201159dd135dc560155e8c16d307ee321681"; +const USDC_TRANSFER = "0x6b212a5069286d710f388b948364452d28b8c33e0f39b8f50b394ff4deff1f03"; + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Base Network - Block Page", () => { + test("genesis block #0 - Base mainnet launch", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.timestampAge).toBeVisible(); + + // Genesis block should have 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #1,000,000 - early Base block with gas details", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["1000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transaction`)).toBeVisible(); + + // Gas Used with percentage + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas (Base always has EIP-1559) + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient (SequencerFeeVault) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #10,000,000 - pre-Ecotone block", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + } + }); + + test("block #25,000,000 - post-Holocene with increased gas limit", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["25000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Should have many transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details - higher gas limit post-Holocene + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + } + }); + + test("genesis block more details section shows correct hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Genesis parent hash (all zeros) - use first() as it appears in multiple places (also in logs bloom) + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); + } + } + }); + + test("block navigation buttons work on Base", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(BASE.blocks["1000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Base Network - Transaction Page", () => { + test("displays Aerodrome DEX swap with all details", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + } + }); + + test("shows correct from and to addresses for swap", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays USDC transfer transaction", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[USDC_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be USDC contract + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction with input data", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Contract interaction should have input data + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays other attributes section", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + } + }); + + test("displays block number link", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// ADDRESS TESTS +// ============================================ + +test.describe("Base Network - Address Page", () => { + test("displays USDC contract details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays USDbC (bridged USDC) contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.usdbc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays WETH predeploy contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.weth; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays AERO token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.aero; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Aerodrome Router contract with details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.aerodromeRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays SequencerFeeVault system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.sequencerFeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GasPriceOracle system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.gasPriceOracle; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L1Block system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.l1Block; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2StandardBridge contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.l2StandardBridge; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2CrossDomainMessenger contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.l2CrossDomainMessenger; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); diff --git a/e2e/tests/bsc.spec.ts b/e2e/tests/bsc.spec.ts new file mode 100644 index 0000000..aaf35c4 --- /dev/null +++ b/e2e/tests/bsc.spec.ts @@ -0,0 +1,832 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { AddressPage } from "../pages/address.page"; +import { BSC } from "../fixtures/bsc"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; + +const CHAIN_ID = BSC.chainId; + +// Transaction hash constants for readability +const BLOCK_20M_TX = "0xad5c9b13688627d670985d68a5be0fadd5f0e34d3ff20e35c655ef4bceec7e7c"; +const DEX_SWAP_TX = "0x0e3384ad2350d20921190b15e29305ed08eecfe97de975b6e015a6c6d476a90a"; +const DEX_AGGREGATOR_TX = "0x874a90a47bc3140adbffff0f4b89da4bea48f9420f97bc5a50e2e478d9a06176"; + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("BSC Block Page", () => { + test("genesis block #0 - BSC mainnet launch", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.timestampAge).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions - genesis has 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Gas Used (0 for genesis) + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=0 (0.0%)")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("genesis block #0 more details shows hash and parent hash", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Click "Show More Details" to expand + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + + // Wait for details to expand + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Verify hash values + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash is all zeros for genesis - use .first() to avoid matching logs bloom + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); + } + } + }); + + test("block #10,000,000 - Pre-Euler block with transactions", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions with count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #10,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #20,000,000 - Post-Euler block with gas details", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #20,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #30,000,000 - Post-Luban with fast finality", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #30,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #40,000,000 - Post-Feynman after BNB Chain Fusion", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["40000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #40,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["40000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #50,000,000 - Post-Maxwell with 0.75s block time", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #50,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block navigation buttons work", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(BSC.blocks["10000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Verify navigation buttons exist + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("BSC Transaction Page", () => { + test("displays transaction from block 20M with all details", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (contract interaction) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("shows correct from and to addresses for block 20M tx", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify from address + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + // Verify to address + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays legacy transaction type correctly (Type 0)", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Legacy transaction should show Transaction Type with value 0 or "Legacy" + // Check for "Type:" which is how the UI displays it + await expect( + page.locator("text=Type:").or(page.locator("text=Transaction Type:")).first() + ).toBeVisible(); + } + }); + + test("displays DEX swap transaction from block 40M", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (DEX swap) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("DEX swap shows correct addresses and gas details", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + + // Verify gas limit is displayed + await expect(page.locator("text=Gas Limit")).toBeVisible(); + } + }); + + test("displays DEX aggregator transaction from block 50M", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_AGGREGATOR_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (contract interaction) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays transaction nonce and position for block 20M tx", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify other attributes section + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value (use label to avoid strict mode issues) + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays DEX swap nonce and position", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays DEX aggregator nonce and position", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_AGGREGATOR_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + + // Verify position value + await expect(page.locator(`text=Position: ${tx.position}`)).toBeVisible(); + } + }); + + test("displays transaction fee", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify transaction fee is displayed + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays block number link for transaction", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// ADDRESS TESTS - BEP20 TOKENS +// ============================================ + +test.describe("BSC Address Page - Tokens", () => { + test("displays WBNB token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.wbnb; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify contract has balance section + await expect( + page.locator("text=Contract Balance:").or(page.locator("text=Balance:")) + ).toBeVisible(); + } + }); + + test("displays USDT (BSC-USD) token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.usdt; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays BUSD token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.busd; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays CAKE (PancakeSwap Token) contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.cake; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays USDC token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays DAI token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.dai; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - DEX CONTRACTS +// ============================================ + +test.describe("BSC Address Page - DEX Contracts", () => { + test("displays PancakeSwap Router v2 contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapRouterV2; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays PancakeSwap Factory v2 contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapFactoryV2; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays PancakeSwap Universal Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - SYSTEM CONTRACTS +// ============================================ + +test.describe("BSC Address Page - System Contracts", () => { + test("displays Validator Set system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.validatorSet; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays System Reward contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.systemReward; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays Token Hub contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.tokenHub; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays Stake Hub contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.stakeHub; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays Governor contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.governor; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("handles invalid address gracefully", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + await addressPage.goto("0xinvalid", CHAIN_ID); + + await expect( + addressPage.errorText + .or(addressPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// ADDRESS TESTS - STAKING CONTRACTS +// ============================================ + +test.describe("BSC Address Page - Staking Contracts", () => { + test("displays PancakeSwap Main Staking contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapStaking; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays PancakeSwap Cake Pool contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapCakePool; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); +}); diff --git a/e2e/tests/mainnet/address.spec.ts b/e2e/tests/mainnet/address.spec.ts new file mode 100644 index 0000000..741c7d1 --- /dev/null +++ b/e2e/tests/mainnet/address.spec.ts @@ -0,0 +1,370 @@ +import { test, expect } from "../../fixtures/test"; +import { AddressPage } from "../../pages/address.page"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForAddressContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; + +test.describe("Address Page", () => { + test("displays address with balance and transaction count", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.vitalik; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify balance section shows ETH + await expect(page.locator("text=Balance:")).toBeVisible(); + const balance = await addressPage.getBalance(); + expect(balance).toContain("ETH"); + + // Verify transaction count is displayed + await expect(page.locator("text=Transactions:")).toBeVisible(); + const txCount = await addressPage.getTransactionCount(); + expect(txCount).toBeTruthy(); + } + }); + + test("shows ENS name for known address", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.vitalik; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded && addr.hasENS) { + // Verify ENS name is displayed + await expect(page.locator(`text=${addr.ensName}`)).toBeVisible(); + // Check for ENS Records section (use first() to avoid strict mode with loading state) + await expect( + page.getByText("ENS Records", { exact: true }).or(page.getByText("ENS Name", { exact: true })) + ).toBeVisible(); + } + }); + + test("identifies contract type for USDC", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.usdc; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + + // Contract should have verification status + const hasVerified = await page.locator("text=Verified").isVisible(); + const hasNotVerified = await page.locator("text=Not Verified").isVisible(); + expect(hasVerified || hasNotVerified).toBe(true); + } + }); + + test("displays contract details for Uniswap Router", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.uniswapRouter; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify contract type is displayed + const type = await addressPage.getAddressType(); + expect(type.toLowerCase()).toMatch(/contract|erc/); + + // Contract should have bytecode section + await expect( + page.locator("text=Contract Bytecode").or(page.locator("text=Bytecode")) + ).toBeVisible(); + } + }); + + test("displays address header with partial address", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.vitalik; + + await addressPage.goto(addr.address); + + // Verify address is displayed in header (at least partial) + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); + + test("handles invalid address gracefully", async ({ page }) => { + const addressPage = new AddressPage(page); + await addressPage.goto("0xinvalid"); + + await expect( + addressPage.errorText + .or(addressPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); + + test("displays ERC721 NFT collection (BAYC) with collection details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.bayc; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as ERC721 (type displays as "ERC-721 NFT") + const type = await addressPage.getAddressType(); + expect(type.toLowerCase()).toMatch(/erc.?721/); + + // Verify collection name and symbol are displayed + await expect(page.locator(`text=${addr.fullName}`).or(page.locator(`text=${addr.name}`))).toBeVisible(); + await expect(page.locator(`text=${addr.symbol}`)).toBeVisible(); + + // Verify ERC721 badge is present (shows as "ERC-721") + await expect(page.locator(".token-standard-badge")).toBeVisible(); + + // Verify token lookup input exists + await expect(page.locator(".erc721-token-input")).toBeVisible(); + + // Verify NFT Collection Details section + await expect(page.locator("text=NFT Collection Details")).toBeVisible(); + + // Contract should have balance displayed + await expect(page.locator("text=Contract Balance:").or(page.locator("text=Balance:"))).toBeVisible(); + + // Verify contract is verified + await expect(page.locator("text=Verified")).toBeVisible(); + } + }); + + test("displays BAYC contract verification details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.bayc; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Contract Details section exists (may be collapsed) + await expect(page.locator("text=Contract Details")).toBeVisible(); + + // Click to expand Contract Details if collapsed + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for expansion and verify details + await expect(page.locator("text=Verified At")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify match type badge + await expect(page.locator(`text=${addr.matchType}`)).toBeVisible(); + + // Verify compiler version + await expect(page.locator("text=Compiler")).toBeVisible(); + await expect(page.locator(`text=${addr.compiler}`)).toBeVisible(); + + // Verify bytecode and source code sections exist + await expect(page.locator("text=Contract Bytecode")).toBeVisible(); + await expect(page.locator("text=Source Code")).toBeVisible(); + } + }); + + test("displays BAYC contract read functions", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.bayc; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Click to expand Contract Details to show functions + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for Read Functions section to be visible + await expect(page.locator("text=/Read Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify some key read functions are displayed + const keyReadFunctions = ["name", "symbol", "totalSupply", "balanceOf", "ownerOf", "tokenURI"]; + for (const fn of keyReadFunctions) { + await expect(page.locator(`button:has-text("${fn}")`).first()).toBeVisible(); + } + } + }); + + test("displays BAYC contract write functions", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.bayc; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Click to expand Contract Details to show functions + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for Write Functions section to be visible + await expect(page.locator("text=/Write Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify some key write functions are displayed + const keyWriteFunctions = ["approve", "transferFrom", "safeTransferFrom"]; + for (const fn of keyWriteFunctions) { + await expect(page.locator(`button:has-text("${fn}")`).first()).toBeVisible(); + } + } + }); + + test("displays BAYC contract events", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.bayc; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Click to expand Contract Details to show events + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for Events section to be visible + await expect(page.locator("text=/Events \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify the events are displayed + for (const event of addr.events) { + await expect(page.locator(`button:has-text("${event}")`).first()).toBeVisible(); + } + } + }); + + test("displays ERC1155 multi-token contract (Rarible) with collection details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.rarible; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Verify it's identified as ERC1155 (type displays as "ERC-1155 MULTI-TOKEN") + const type = await addressPage.getAddressType(); + expect(type.toLowerCase()).toMatch(/erc.?1155/); + + // Verify collection name and symbol are displayed + await expect(page.locator(`text=${addr.name}`).or(page.locator(`text=${addr.symbol}`))).toBeVisible(); + + // Verify ERC1155 badge is present + await expect(page.locator(".token-standard-badge")).toBeVisible(); + + // Verify token lookup input exists + await expect(page.locator(".erc1155-token-input")).toBeVisible(); + + // Verify Multi-Token Collection Details section + await expect(page.locator("text=Multi-Token Collection Details")).toBeVisible(); + + // Contract should have balance displayed + await expect(page.locator("text=Contract Balance:").or(page.locator("text=Balance:"))).toBeVisible(); + + // Verify contract is verified + await expect(page.locator("text=Verified")).toBeVisible(); + } + }); + + test("displays Rarible contract verification details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.rarible; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Contract Details section exists (may be collapsed) + await expect(page.locator("text=Contract Details")).toBeVisible(); + + // Click to expand Contract Details if collapsed + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for expansion and verify details + await expect(page.locator("text=Verified At")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify match type badge (MATCH for Rarible) + await expect(page.locator(`text=${addr.matchType}`)).toBeVisible(); + + // Verify compiler version + await expect(page.locator("text=Compiler")).toBeVisible(); + await expect(page.locator(`text=${addr.compiler}`)).toBeVisible(); + + // Verify bytecode and source code sections exist + await expect(page.locator("text=Contract Bytecode")).toBeVisible(); + await expect(page.locator("text=Source Code")).toBeVisible(); + } + }); + + test("displays Rarible contract read functions", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.rarible; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Click to expand Contract Details to show functions + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for Read Functions section to be visible + await expect(page.locator("text=/Read Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify some key read functions are displayed + const keyReadFunctions = ["balanceOf", "name", "symbol", "uri", "supportsInterface"]; + for (const fn of keyReadFunctions) { + await expect(page.locator(`button:has-text("${fn}")`).first()).toBeVisible(); + } + } + }); + + test("displays Rarible contract write functions", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.rarible; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Click to expand Contract Details to show functions + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for Write Functions section to be visible + await expect(page.locator("text=/Write Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify some key write functions are displayed + const keyWriteFunctions = ["mint", "burn", "safeTransferFrom", "setApprovalForAll"]; + for (const fn of keyWriteFunctions) { + await expect(page.locator(`button:has-text("${fn}")`).first()).toBeVisible(); + } + } + }); + + test("displays Rarible contract events", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = MAINNET.addresses.rarible; + + await addressPage.goto(addr.address); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + // Click to expand Contract Details to show events + const contractDetailsHeader = page.locator("text=Contract Details").first(); + await contractDetailsHeader.click(); + + // Wait for Events section to be visible + await expect(page.locator("text=/Events \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); + + // Verify some key events are displayed + const keyEvents = ["TransferSingle", "TransferBatch", "ApprovalForAll", "URI"]; + for (const event of keyEvents) { + await expect(page.locator(`button:has-text("${event}")`).first()).toBeVisible(); + } + } + }); +}); diff --git a/e2e/tests/mainnet/block.spec.ts b/e2e/tests/mainnet/block.spec.ts new file mode 100644 index 0000000..e3e098d --- /dev/null +++ b/e2e/tests/mainnet/block.spec.ts @@ -0,0 +1,254 @@ +import { test, expect } from "../../fixtures/test"; +import { BlockPage } from "../../pages/block.page"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForBlockContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; + +test.describe("Block Page", () => { + test("block #10,000 - pre-London block with no transactions", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = MAINNET.blocks["10000"]; + await blockPage.goto(block.number); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.timestampAge).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + + // Gas Used with value (0 (0.0%)) + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=0 (0.0%)")).toBeVisible(); + + // Gas Limit with value + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Extra Data with value + await expect(page.locator("text=Extra Data:")).toBeVisible(); + await expect(page.locator(`text=${block.extraData}`)).toBeVisible(); + + // Difficulty with value + await expect(page.getByText("Difficulty:", { exact: true })).toBeVisible(); + await expect(page.getByText(block.difficulty).first()).toBeVisible(); + + // Total Difficulty with value + await expect(page.getByText("Total Difficulty:", { exact: true })).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Should NOT have base fee (pre-London) + await expect(page.locator("text=Base Fee Per Gas:")).not.toBeVisible(); + } + }); + + test("block #1,000,000 - pre-London block with transactions", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = MAINNET.blocks["1000000"]; + await blockPage.goto(block.number); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions with count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit with value + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Extra Data + await expect(page.locator("text=Extra Data:")).toBeVisible(); + + // Difficulty with value + await expect(page.getByText("Difficulty:", { exact: true })).toBeVisible(); + await expect(page.getByText(block.difficulty).first()).toBeVisible(); + + // Total Difficulty with value + await expect(page.getByText("Total Difficulty:", { exact: true })).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Should NOT have base fee (pre-London) + await expect(page.locator("text=Base Fee Per Gas:")).not.toBeVisible(); + } + }); + + test("block #20,000,000 - post-London block with base fee and withdrawals", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = MAINNET.blocks["20000000"]; + await blockPage.goto(block.number); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions with count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Withdrawals with count + await expect(page.locator("text=Withdrawals:")).toBeVisible(); + await expect(page.locator(`text=${block.withdrawals} withdrawals in this block`)).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit with value + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Base Fee Per Gas with value (post-London) + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + await expect(page.locator(`text=${block.baseFeePerGas}`)).toBeVisible(); + + // Burnt Fees with value + await expect(page.locator("text=Burnt Fees:")).toBeVisible(); + await expect(page.locator(`text=${block.burntFees}`)).toBeVisible(); + + // Extra Data with value + await expect(page.locator("text=Extra Data:")).toBeVisible(); + await expect(page.locator(`text=${block.extraData}`)).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + } + }); + + test("block #10,000 more details section shows correct hash values", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = MAINNET.blocks["10000"]; + await blockPage.goto(block.number); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Click "Show More Details" to expand + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + + // Wait for details to expand (button text changes) + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels are present (source text is title case, CSS transforms to uppercase) + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("State Root:", { exact: true })).toBeVisible(); + await expect(page.getByText("Transactions Root:", { exact: true })).toBeVisible(); + await expect(page.getByText("Receipts Root:", { exact: true })).toBeVisible(); + await expect(page.getByText("Nonce:", { exact: true })).toBeVisible(); + await expect(page.getByText("Mix Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Sha3 Uncles:", { exact: true })).toBeVisible(); + + // Verify actual hash values (use .first() for values that may appear multiple times) + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + await expect(page.locator(`text=${block.stateRoot}`)).toBeVisible(); + // transactionsRoot and receiptsRoot are identical for empty blocks, use .first() + await expect(page.locator(`text=${block.transactionsRoot}`).first()).toBeVisible(); + await expect(page.locator(`text=${block.nonce}`)).toBeVisible(); + await expect(page.locator(`text=${block.mixHash}`)).toBeVisible(); + await expect(page.locator(`text=${block.sha3Uncles}`)).toBeVisible(); + } + } + }); + + test("block #20,000,000 more details section includes withdrawals root", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = MAINNET.blocks["20000000"]; + await blockPage.goto(block.number); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Click "Show More Details" to expand + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + + // Wait for details to expand + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels including withdrawals root (post-Shanghai) + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("State Root:", { exact: true })).toBeVisible(); + await expect(page.getByText("Transactions Root:", { exact: true })).toBeVisible(); + await expect(page.getByText("Receipts Root:", { exact: true })).toBeVisible(); + await expect(page.getByText("Withdrawals Root:", { exact: true })).toBeVisible(); + await expect(page.getByText("Nonce:", { exact: true })).toBeVisible(); + await expect(page.getByText("Mix Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Sha3 Uncles:", { exact: true })).toBeVisible(); + + // Verify actual hash values + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + await expect(page.locator(`text=${block.stateRoot}`)).toBeVisible(); + await expect(page.locator(`text=${block.transactionsRoot}`)).toBeVisible(); + await expect(page.locator(`text=${block.receiptsRoot}`)).toBeVisible(); + await expect(page.locator(`text=${block.withdrawalsRoot}`)).toBeVisible(); + await expect(page.locator(`text=${block.nonce}`)).toBeVisible(); + await expect(page.locator(`text=${block.mixHash}`)).toBeVisible(); + await expect(page.locator(`text=${block.sha3Uncles}`)).toBeVisible(); + } + } + }); + + test("block navigation buttons work", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(MAINNET.blocks["1000000"].number); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Verify navigation buttons exist + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); diff --git a/e2e/tests/mainnet/token.spec.ts b/e2e/tests/mainnet/token.spec.ts new file mode 100644 index 0000000..7d05de1 --- /dev/null +++ b/e2e/tests/mainnet/token.spec.ts @@ -0,0 +1,277 @@ +import { test, expect } from "../../fixtures/test"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForTokenContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; + +test.describe("ERC721 Token Details", () => { + test("displays BAYC #1 NFT details section", async ({ page }, testInfo) => { + const token = MAINNET.tokens.baycToken1; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify ERC721 header is displayed + await expect(page.locator(".erc721-header")).toBeVisible(); + + // Verify token ID is displayed in title + await expect(page.locator(".erc721-header-title")).toContainText(`#${token.tokenId}`); + + // Verify collection name is displayed + await expect( + page.locator(`text=${token.collectionName}`).or(page.locator(`text=${token.collectionSymbol}`)) + ).toBeVisible(); + + // Verify NFT Details section + await expect(page.locator("text=NFT Details")).toBeVisible(); + + // Verify Token ID is shown + await expect(page.locator("text=Token ID:")).toBeVisible(); + await expect(page.locator(`text=${token.tokenId}`).first()).toBeVisible(); + + // Verify Token Standard badge + await expect(page.locator("text=Token Standard:")).toBeVisible(); + await expect(page.locator(".token-standard-badge")).toBeVisible(); + + // Verify Collection Size + await expect(page.locator("text=Collection Size:")).toBeVisible(); + await expect(page.locator(`text=${token.collectionSize}`)).toBeVisible(); + + // Verify Owner is displayed + await expect(page.locator("text=Owner:")).toBeVisible(); + } + }); + + test("displays BAYC #1 token image", async ({ page }, testInfo) => { + const token = MAINNET.tokens.baycToken1; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify image container exists + await expect(page.locator(".erc721-image-container")).toBeVisible(); + + // Verify token image is rendered + await expect(page.locator(".erc721-token-image")).toBeVisible(); + } + }); + + test("displays BAYC #1 properties/attributes", async ({ page }, testInfo) => { + const token = MAINNET.tokens.baycToken1; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify Properties section exists + await expect(page.locator("text=Properties")).toBeVisible(); + + // Verify properties count (5 for BAYC #1) + await expect(page.locator("text=/Properties\\s+5/").or(page.locator("text=Properties 5"))).toBeVisible(); + + // Verify specific properties are displayed + for (const prop of token.properties) { + await expect(page.locator(`.erc721-attribute-type:has-text("${prop.trait}")`).first()).toBeVisible(); + await expect(page.locator(`.erc721-attribute-value:has-text("${prop.value}")`).first()).toBeVisible(); + } + } + }); + + test("displays BAYC #1 Token URI section", async ({ page }, testInfo) => { + const token = MAINNET.tokens.baycToken1; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify Token URI section exists + await expect(page.locator("text=Token URI")).toBeVisible(); + + // Verify the IPFS URI is displayed + await expect(page.locator(`text=${token.tokenUri}`)).toBeVisible(); + + // Verify Open URI button exists + await expect(page.locator("text=Open URI")).toBeVisible(); + } + }); + + test("displays BAYC #1 Raw Metadata section", async ({ page }, testInfo) => { + const token = MAINNET.tokens.baycToken1; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify Raw Metadata section exists (expandable) + await expect(page.locator("text=Raw Metadata")).toBeVisible(); + } + }); + + test("displays BAYC #100 with different properties", async ({ page }, testInfo) => { + const token = MAINNET.tokens.baycToken100; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify token ID is displayed + await expect(page.locator(".erc721-header-title")).toContainText(`#${token.tokenId}`); + + // Verify Properties section exists with 5 properties + await expect(page.locator("text=Properties")).toBeVisible(); + + // Verify specific properties for token #100 + for (const prop of token.properties) { + await expect(page.locator(`.erc721-attribute-type:has-text("${prop.trait}")`).first()).toBeVisible(); + await expect(page.locator(`.erc721-attribute-value:has-text("${prop.value}")`).first()).toBeVisible(); + } + + // Verify Token URI matches token #100 + await expect(page.locator(`text=${token.tokenUri}`)).toBeVisible(); + } + }); + + test("navigates to ERC721 token via collection lookup", async ({ page }, testInfo) => { + const addr = MAINNET.addresses.bayc; + const tokenId = "1"; + + // Go to collection page + await page.goto(`/${MAINNET.chainId}/address/${addr.address}`); + + // Wait for page to load (may show error due to RPC issues) + await expect( + page + .locator(".erc721-token-input") + .or(page.locator("text=Error:")) + .or(page.locator("text=Failed to fetch")) + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 5 }); + + // Only proceed if the token input is visible (page loaded successfully) + if (await page.locator(".erc721-token-input").isVisible()) { + // Enter token ID in lookup input + await page.locator(".erc721-token-input").fill(tokenId); + + // Click view button + await page.locator(".erc721-view-button").click(); + + // Verify navigation to token details page + await expect(page).toHaveURL(new RegExp(`/${MAINNET.chainId}/address/${addr.address}/${tokenId}`)); + + // Verify token details page loaded + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + await expect(page.locator(".erc721-header")).toBeVisible(); + } + } + }); +}); + +test.describe("ERC1155 Token Details", () => { + test("displays ERC1155 token details page", async ({ page }, testInfo) => { + const token = MAINNET.tokens.raribleToken; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify ERC1155 header is displayed + await expect(page.locator(".erc1155-header")).toBeVisible(); + + // Verify collection name is displayed + await expect( + page.locator(`text=${token.collectionName}`).or(page.locator(`text=${token.collectionSymbol}`)) + ).toBeVisible(); + } + }); + + test("displays ERC1155 token image container", async ({ page }, testInfo) => { + const token = MAINNET.tokens.raribleToken; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify image container exists + await expect(page.locator(".erc1155-image-container")).toBeVisible(); + } + }); + + test("displays ERC1155 balance lookup section", async ({ page }, testInfo) => { + const token = MAINNET.tokens.raribleToken; + await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); + + const loaded = await waitForTokenContent(page, testInfo); + if (loaded) { + // Verify balance lookup section exists (unique to ERC1155) + await expect(page.locator(".erc1155-balance-lookup")).toBeVisible(); + + // Verify balance input field exists + await expect(page.locator(".erc1155-balance-input")).toBeVisible(); + + // Verify check balance button exists + await expect(page.locator(".erc1155-balance-button")).toBeVisible(); + } + }); + + test("navigates to ERC1155 token via collection lookup", async ({ page }, testInfo) => { + const addr = MAINNET.addresses.rarible; + const tokenId = "1"; // Simple token ID for navigation test + + // Go to collection page + await page.goto(`/${MAINNET.chainId}/address/${addr.address}`); + + // Wait for page to load - could be ERC1155 view, generic contract, or error + await expect( + page + .locator(".erc1155-token-input") + .or(page.locator("text=Contract Details")) + .or(page.locator("text=Error:")) + .or(page.locator("text=Failed to fetch")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 5 }); + + // Only proceed if the ERC1155 token input is visible (contract detected as ERC1155) + if (await page.locator(".erc1155-token-input").isVisible()) { + // Enter token ID in lookup input + await page.locator(".erc1155-token-input").fill(tokenId); + + // Click view button + await page.locator(".erc1155-view-button").click(); + + // Verify navigation to token details page + await expect(page).toHaveURL(new RegExp(`/${MAINNET.chainId}/address/${addr.address}/${tokenId}`)); + + // Verify token details page loaded (may be ERC1155 or show loading/error for invalid token) + await expect( + page.locator(".erc1155-header").or(page.locator("text=Error:")).or(page.locator(".erc1155-detail-content")) + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + } + // If not detected as ERC1155, test passes (RPC may not support interface detection) + }); +}); + +test.describe("Token Details - Error Handling", () => { + test("handles invalid token ID gracefully for ERC721", async ({ page }, testInfo) => { + const addr = MAINNET.addresses.bayc; + const invalidTokenId = "999999999"; // Non-existent token + + await page.goto(`/${MAINNET.chainId}/address/${addr.address}/${invalidTokenId}`); + + // Should show error or handle gracefully + await expect( + page + .locator("text=Error:") + .or(page.locator("text=Something went wrong")) + .or(page.locator(".erc721-header")) + .or(page.locator(".erc721-detail-content")) + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); + + test("handles invalid contract for token view", async ({ page }, testInfo) => { + const invalidContract = "0x0000000000000000000000000000000000000000"; + const tokenId = "1"; + + await page.goto(`/${MAINNET.chainId}/address/${invalidContract}/${tokenId}`); + + // Should show error or handle gracefully + await expect( + page + .locator("text=Error:") + .or(page.locator("text=Something went wrong")) + .or(page.locator(".container-wide")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); diff --git a/e2e/tests/mainnet/transaction.spec.ts b/e2e/tests/mainnet/transaction.spec.ts new file mode 100644 index 0000000..e1441c6 --- /dev/null +++ b/e2e/tests/mainnet/transaction.spec.ts @@ -0,0 +1,120 @@ +import { test, expect } from "../../fixtures/test"; +import { TransactionPage } from "../../pages/transaction.page"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForTxContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; + +// Transaction hash constants for readability +const FIRST_ETH_TRANSFER = "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"; +const USDC_APPROVAL = "0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef"; + +test.describe("Transaction Page", () => { + test("displays first ETH transfer with all details", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; + + await txPage.goto(tx.hash); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + await expect(page.locator("text=Value:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Price:")).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + } + }); + + test("shows correct from and to addresses", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; + + await txPage.goto(tx.hash); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction value and fee", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; + + await txPage.goto(tx.hash); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify value contains ETH + const value = await txPage.getValue(); + expect(value).toContain("ETH"); + + // Verify transaction fee is displayed + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays other attributes section", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; + + await txPage.goto(tx.hash); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify other attributes section + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + } + }); + + test("displays transaction with input data", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = MAINNET.transactions[USDC_APPROVAL]; + + await txPage.goto(tx.hash); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify input data section exists for contract interactions + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays block number link", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; + + await txPage.goto(tx.hash); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify block section is displayed (block number may be formatted with commas) + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid"); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); diff --git a/e2e/tests/optimism.spec.ts b/e2e/tests/optimism.spec.ts new file mode 100644 index 0000000..779e584 --- /dev/null +++ b/e2e/tests/optimism.spec.ts @@ -0,0 +1,735 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { AddressPage } from "../pages/address.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { OPTIMISM } from "../fixtures/optimism"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; + +const CHAIN_ID = OPTIMISM.chainId; + +// Transaction hash constants for readability +const VELODROME_SWAP = "0xa8d73ea0639f39157f787a29591b36fc73c19b443bbe8416d8d6f24858063910"; +const OP_TRANSFER = "0xdcf7c4afb479cd47f7ce263cbbb298f559b81fc592cc07737935a6166fb90f0c"; +const SYSTEM_TX = "0x5d3522dad0d0745b59e9443733f8423548f99856c00768aba9779ae288dedd0a"; + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Optimism - Block Page", () => { + test("genesis block #0 - Optimism mainnet (post-regenesis)", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.timestampAge).toBeVisible(); + + // Genesis block has 8,893 transactions from state migration + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + } + }); + + test("block #100,000,000 - pre-Bedrock block with gas details", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transaction`)).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + } + }); + + test("block #100,000,000 more details section shows correct hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #110,000,000 - post-Bedrock with complete gas details", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["110000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Base Fee Per Gas (post-Bedrock) + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient (SequencerFeeVault) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #110,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["110000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #120,000,000 - post-Ecotone (EIP-4844) high utilization", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["120000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Should have many transactions (21) + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details - high utilization block (91.9%) + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #120,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["120000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #130,000,000 - post-Holocene with increased gas limit (60M)", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["130000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit - increased post-Holocene (60M) + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #130,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["130000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("genesis block more details section shows correct hash", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Genesis parent hash (all zeros) - use first() as it appears in multiple places + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); + } + } + }); + + test("block navigation buttons work on Optimism", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(OPTIMISM.blocks["110000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Optimism - Transaction Page", () => { + test("displays Velodrome DEX swap (Legacy Type 0) with all details", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + } + }); + + test("shows correct from and to addresses for Velodrome swap", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction fee for Velodrome swap", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify transaction fee is displayed + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays Velodrome swap nonce and position", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value is displayed (use locator that includes the label) + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays OP token transfer transaction (EIP-1559 Type 2)", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[OP_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be OP token contract + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("OP transfer shows correct addresses and gas details", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[OP_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + + // Verify gas limit is displayed + await expect(page.locator("text=Gas Limit")).toBeVisible(); + } + }); + + test("OP transfer shows nonce and position", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[OP_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value is displayed (use locator that includes the label) + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays system transaction (Type 126) - L2CrossDomainMessenger relay", async ({ + page, + }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[SYSTEM_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be L2CrossDomainMessenger + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("system transaction shows correct from address (Aliased L1 Messenger)", async ({ + page, + }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[SYSTEM_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction with input data", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Contract interaction should have input data + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays block number link for transaction", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// ADDRESS TESTS +// ============================================ + +test.describe("Optimism - Address Page", () => { + test("displays native USDC contract details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays bridged USDC.e contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.usdce; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays WETH predeploy contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.weth; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays OP governance token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.op; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays USDT contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.usdt; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays DAI contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.dai; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Velodrome Router contract with details", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.velodromeRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details").first()).toBeVisible(); + } + }); + + test("displays Velodrome Universal Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.velodromeUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Uniswap V3 Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.uniswapV3Router; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Uniswap Universal Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.uniswapUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays SequencerFeeVault system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.sequencerFeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GasPriceOracle system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.gasPriceOracle; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L1Block system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l1Block; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2StandardBridge contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l2StandardBridge; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2CrossDomainMessenger contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l2CrossDomainMessenger; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2ToL1MessagePasser contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l2ToL1MessagePasser; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays BaseFeeVault system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.baseFeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L1FeeVault system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l1FeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); diff --git a/e2e/tests/polygon.spec.ts b/e2e/tests/polygon.spec.ts new file mode 100644 index 0000000..3d2e960 --- /dev/null +++ b/e2e/tests/polygon.spec.ts @@ -0,0 +1,744 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { AddressPage } from "../pages/address.page"; +import { POLYGON } from "../fixtures/polygon"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; + +const CHAIN_ID = POLYGON.chainId; + +// Transaction hash constants for readability +const LEGACY_NFT_TX = "0xb14598e46791c2f0ab366ba2fd4a533e21a0c9894f902773e02e3869b7373c3e"; +const DEFI_SWAP_TX = "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0"; +const CONTRACT_TX = "0x65edbf03a20a0317295efaeb9c20836b20b16740c8311ce51ceee91d7674b20d"; + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Polygon Block Page", () => { + test("genesis block #0 - Polygon PoS mainnet launch", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Genesis has 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("genesis block #0 more details shows hash and parent hash", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Genesis parent hash (all zeros) - use .first() to avoid matching logs bloom + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); + } + } + }); + + test("block #10,000,000 - Early Polygon activity", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #10,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #20,000,000 - Growing DeFi activity", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #20,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #30,000,000 - Mature network", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #30,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #38,189,056 - Delhi Hard Fork", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["38189056"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #38,189,056 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["38189056"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #50,000,000 - High activity block", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #50,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #62,278,656 - Ahmedabad Hard Fork (MATIC to POL)", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["62278656"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #62,278,656 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["62278656"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #65,000,000 - Post-Ahmedabad POL era", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["65000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #65,000,000 more details shows hashes", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["65000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block page loads successfully", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(POLYGON.blocks["20000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page, testInfo); + if (loaded) { + // Verify block page loaded with expected content + await expect(blockPage.blockNumber).toBeVisible(); + await expect(page.locator("text=Transactions:")).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }, testInfo) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Polygon Transaction Page", () => { + test("displays legacy NFT transaction from block 30M", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (NFT transfer) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("shows correct from and to addresses for legacy NFT tx", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays legacy transaction type correctly (Type 0)", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect( + page.locator("text=Type:").or(page.locator("text=Transaction Type:")).first() + ).toBeVisible(); + } + }); + + test("displays DeFi swap transaction from block 50M", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("DeFi swap shows correct addresses", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays contract interaction from block 65M", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[CONTRACT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays transaction nonce and position for legacy NFT tx", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify position value (nonce is very large, just check it's displayed) + await expect(page.locator(`text=Position: ${tx.position}`)).toBeVisible(); + } + }); + + test("displays DeFi swap nonce and position", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays contract tx nonce and position", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[CONTRACT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + await expect(page.locator(`text=Position: ${tx.position}`)).toBeVisible(); + } + }); + + test("displays transaction fee", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays block number link for transaction", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); + +// ============================================ +// ADDRESS TESTS - ERC20 TOKENS +// ============================================ + +test.describe("Polygon Address Page - Tokens", () => { + test("displays WPOL (Wrapped POL) token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.wpol; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + // Contract should show code or token info + await expect( + page.locator("text=Contract").or(page.locator("text=Token")).first() + ).toBeVisible(); + } + }); + + test("displays USDC.e (Bridged USDC) token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.usdc; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays Native USDC token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.usdcNative; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays USDT token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.usdt; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays WETH token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.weth; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays DAI token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.dai; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays AAVE token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.aave; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays LINK token contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.link; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - DEX CONTRACTS +// ============================================ + +test.describe("Polygon Address Page - DEX Contracts", () => { + test("displays QuickSwap Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.quickswapRouter; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + await expect( + page.locator("text=Contract").or(page.locator("text=Code")).first() + ).toBeVisible(); + } + }); + + test("displays Uniswap V3 Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.uniswapV3Router; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays SushiSwap Router contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.sushiswapRouter; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - NFT & LENDING +// ============================================ + +test.describe("Polygon Address Page - NFT & Lending", () => { + test("displays OpenSea Storefront contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.openseaStorefront; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays Aave V3 Pool contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.aaveV3Pool; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - SYSTEM CONTRACTS +// ============================================ + +test.describe("Polygon Address Page - System Contracts", () => { + test("displays POL Token system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.maticToken; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays StateReceiver system contract", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.stateReceiver; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page, testInfo); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("handles invalid address gracefully", async ({ page }, testInfo) => { + const addressPage = new AddressPage(page); + await addressPage.goto("0xinvalid", CHAIN_ID); + + await expect( + addressPage.errorText + .or(addressPage.container) + .or(page.locator("text=Something went wrong")) + .or(page.locator("text=Invalid")) + .first() + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); + }); +}); diff --git a/hardhat-node-test/contracts/OpenScanPayment.sol b/hardhat-node-test/contracts/OpenScanPayment.sol new file mode 100644 index 0000000..12ba73f --- /dev/null +++ b/hardhat-node-test/contracts/OpenScanPayment.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract OpenScanPayment is Ownable { + + event PaymentReceived(address indexed payer, uint256 amount); + event DonationReceived(address indexed donor, uint256 amount); + + constructor(address initialOwner) Ownable(initialOwner) {} + + // Pay to OpenScan with a signature to be used later for verification + // After payment you will need to provide the a signature off-chain of the tx hash + // The tx hash has to be signed by the sender of the payment + // The signature has to be provided on the pull request on the explorer-metadata repository + function pay() public payable { + require(msg.value > 0, "pay: payment must be greater than zero"); + emit PaymentReceived(msg.sender, msg.value); + (bool success, ) = owner().call{value: msg.value}(""); + require(success, "transfer failed"); + } + + // Donate to OpenScan with a message to be shown publicly + // WARNING: Messages are subject to moderation and may not be displayed + function donate(string calldata message) public payable { + require(msg.value > 0, "donate: donation must be greater than zero"); + emit DonationReceived(msg.sender, msg.value); + (bool success, ) = owner().call{value: msg.value}(""); + require(success, "transfer failed"); + // message is stored in calldata for off-chain retrieval + } + + // Execute arbitrary call (onlyOwner) to recover funds and tokens + function executeCall(address target, bytes memory data) public onlyOwner returns (bytes memory) { + (bool success, bytes memory returnData) = target.call(data); + require(success, "executeCall: call failed"); + return returnData; + } +} diff --git a/hardhat-node-test/ignition/modules/TestSuite.ts b/hardhat-node-test/ignition/modules/TestSuite.ts index 0711132..a3c18f9 100644 --- a/hardhat-node-test/ignition/modules/TestSuite.ts +++ b/hardhat-node-test/ignition/modules/TestSuite.ts @@ -44,6 +44,10 @@ export default buildModule("TestSuiteModule", (m) => { // Deploy Counter const counter = m.contract("Counter"); + // Deploy OpenScanPayment + const deployer = m.getAccount(0); + const openScanPayment = m.contract("OpenScanPayment", [deployer]); + return { tokenA, tokenB, @@ -53,5 +57,6 @@ export default buildModule("TestSuiteModule", (m) => { vault, multicall, counter, + openScanPayment, }; }); diff --git a/hardhat-node-test/package-lock.json b/hardhat-node-test/package-lock.json index 827f185..38556b8 100644 --- a/hardhat-node-test/package-lock.json +++ b/hardhat-node-test/package-lock.json @@ -1,2925 +1,2934 @@ { - "name": "hardhat-test", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "hardhat-test", - "version": "1.0.0", - "devDependencies": { - "@nomicfoundation/hardhat-ignition": "^3.0.5", - "@nomicfoundation/hardhat-toolbox-viem": "^5.0.1", - "@types/node": "^22.19.1", - "forge-std": "github:foundry-rs/forge-std#v1.9.4", - "hardhat": "^3.0.15", - "typescript": "~5.8.0", - "viem": "^2.39.3" - } - }, - "node_modules/@actions/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", - "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@actions/exec": "^1.1.1", - "@actions/http-client": "^2.0.1" - } - }, - "node_modules/@actions/exec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", - "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@actions/io": "^1.0.1" - } - }, - "node_modules/@actions/http-client": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", - "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "tunnel": "^0.0.6", - "undici": "^5.25.4" - } - }, - "node_modules/@actions/io": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", - "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@ethersproject/abi": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", - "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "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" - } - }, - "node_modules/@ethersproject/abstract-provider": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", - "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "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" - } - }, - "node_modules/@ethersproject/abstract-signer": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", - "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "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" - } - }, - "node_modules/@ethersproject/address": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", - "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "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" - } - }, - "node_modules/@ethersproject/base64": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", - "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0" - } - }, - "node_modules/@ethersproject/bignumber": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", - "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "bn.js": "^5.2.1" - } - }, - "node_modules/@ethersproject/bytes": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", - "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/constants": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", - "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bignumber": "^5.8.0" - } - }, - "node_modules/@ethersproject/hash": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", - "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "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" - } - }, - "node_modules/@ethersproject/keccak256": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", - "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "js-sha3": "0.8.0" - } - }, - "node_modules/@ethersproject/logger": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", - "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT" - }, - "node_modules/@ethersproject/networks": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", - "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/properties": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", - "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/rlp": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", - "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/signing-key": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", - "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "bn.js": "^5.2.1", - "elliptic": "6.6.1", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/strings": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", - "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/transactions": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", - "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "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" - } - }, - "node_modules/@ethersproject/web": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", - "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "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" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@noble/ciphers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", - "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@nomicfoundation/edr": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.12.0-next.14.tgz", - "integrity": "sha512-MGHY2x7JaNdkqlQxFBYoM7Miw2EqsQrI3ReVZMwLP5mULSRTAOnt3hCw6cnjXxGi991HnejNAedJofke6OdqqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.12.0-next.14", - "@nomicfoundation/edr-darwin-x64": "0.12.0-next.14", - "@nomicfoundation/edr-linux-arm64-gnu": "0.12.0-next.14", - "@nomicfoundation/edr-linux-arm64-musl": "0.12.0-next.14", - "@nomicfoundation/edr-linux-x64-gnu": "0.12.0-next.14", - "@nomicfoundation/edr-linux-x64-musl": "0.12.0-next.14", - "@nomicfoundation/edr-win32-x64-msvc": "0.12.0-next.14" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.12.0-next.14.tgz", - "integrity": "sha512-sl0DibKSUOS7JXhUtaQ6FJUY+nk+uq5gx+Fyd9iiqs8awZPNn6KSuvV1EbWCi+yd3mrxgZ/wO8E77C1Dxj4xQA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.12.0-next.14.tgz", - "integrity": "sha512-lfmatc1MSOaw0rDFB+ynnAGz5TWm3hSeY/+zDpPZghMODZelXm4JCqF41CQ6paLsW3X/pXcHM1HUGCUBWeoI/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.12.0-next.14.tgz", - "integrity": "sha512-sWun3PhVgat8d4lg1d5MAXSIsFlSMBzvrpMSDFNOU9hPJEclSHbHBMRcarQuGqwm/5ZBzTwCS25u78A+UATTrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.12.0-next.14.tgz", - "integrity": "sha512-omWKioD8fFp7ayCeSDu2CqvG78+oYw8zdVECDwZVmE0jpszRCsTufNYflWRQnlGqH6GqjEUwq2c3yLxFgOTjFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.12.0-next.14.tgz", - "integrity": "sha512-vk0s4SaC7s1wa98W24a4zqunTK/yIcSEnsSLRM/Nl+JJs6iqS8tvmnh/BbFINORMBJ065OWc10qw2Lsbu/rxtg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.12.0-next.14.tgz", - "integrity": "sha512-/xKQD6c2RXQBIb30iTeh/NrMdYvHs6Nd+2UXS6wxlfX7GzRPOkpVDiDGD7Sda82JI459KH67dADOD6CpX8cpHQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.12.0-next.14", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.12.0-next.14.tgz", - "integrity": "sha512-GZcyGdOoLWnUtfPU+6B1vUi4fwf3bouSRf3xuKFHz3p/WNhpDK+8Esq3UmOmYAZWRgFT0ZR6XUk9H2owGDTVvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@nomicfoundation/hardhat-errors": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-errors/-/hardhat-errors-3.0.5.tgz", - "integrity": "sha512-8Ayqf6hFM1glmrSxrXgX6n2pn5uTlHNxEB8N5Me0DOeOGB67PRIrQdiO+RzUhrNW5YgWUNWBevOLQbW06uQ79g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nomicfoundation/hardhat-utils": "^3.0.1" - } - }, - "node_modules/@nomicfoundation/hardhat-ignition": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition/-/hardhat-ignition-3.0.5.tgz", - "integrity": "sha512-ZTaGHdDDuHE5MjlRaSbSIOuTSZcJtVd/ShEyE6E1PEt0FLTcgYZu+NNCWRc2JZG6/Cix0PAO297y+yi+USNilA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nomicfoundation/hardhat-errors": "^3.0.2", - "@nomicfoundation/hardhat-utils": "^3.0.5", - "@nomicfoundation/ignition-core": "^3.0.5", - "@nomicfoundation/ignition-ui": "^3.0.5", - "chalk": "^5.3.0", - "debug": "^4.3.2", - "json5": "^2.2.3", - "prompts": "^2.4.2" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-verify": "^3.0.0", - "hardhat": "^3.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-ignition-viem": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition-viem/-/hardhat-ignition-viem-3.0.5.tgz", - "integrity": "sha512-bK+Rmg7m6wJxoqXOTxl/HA6AakdS6OUJjGUUYxfFpuXCH9c4tW3Qp7fpgAhI0W3cCPAeT42OANSDlCH/MRZbBA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nomicfoundation/hardhat-errors": "^3.0.2" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-ignition": "^3.0.5", - "@nomicfoundation/hardhat-verify": "^3.0.0", - "@nomicfoundation/hardhat-viem": "^3.0.0", - "@nomicfoundation/ignition-core": "^3.0.5", - "hardhat": "^3.0.0", - "viem": "^2.30.0" - } - }, - "node_modules/@nomicfoundation/hardhat-keystore": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-keystore/-/hardhat-keystore-3.0.3.tgz", - "integrity": "sha512-rkwfdy/GsX/2SV49RGBvMsCuR+SYGJQGD3wcrS5m2Cyap5eQFEgKZbqpua6YQRA2raxRmVVH6antIIftgBFXAQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/ciphers": "1.2.1", - "@noble/hashes": "1.7.1", - "@nomicfoundation/hardhat-errors": "^3.0.0", - "@nomicfoundation/hardhat-utils": "^3.0.5", - "@nomicfoundation/hardhat-zod-utils": "^3.0.0", - "chalk": "^5.3.0", - "debug": "^4.3.2", - "zod": "^3.23.8" - }, - "peerDependencies": { - "hardhat": "^3.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-network-helpers": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-3.0.3.tgz", - "integrity": "sha512-FqXD8CPFNdluEhELqNV/Q0grOQtlwRWr28LW+/NTas3rrDAXpNOIPCCq3RIXJIqsdbNPQsG2FpnfKj9myqIsKQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nomicfoundation/hardhat-errors": "^3.0.5", - "@nomicfoundation/hardhat-utils": "^3.0.5" - }, - "peerDependencies": { - "hardhat": "^3.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-node-test-reporter": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-node-test-reporter/-/hardhat-node-test-reporter-3.0.1.tgz", - "integrity": "sha512-p6yNKZFnJ2OMplXx7zi45KGWr4hr/qMkg+gTuSSLLlph7NL1DGjGG+N6GrZs46AGSrsnYEocKXGnavl92dxEig==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@actions/core": "^1.10.1", - "chalk": "^5.3.0", - "jest-diff": "^29.7.0" - } - }, - "node_modules/@nomicfoundation/hardhat-node-test-runner": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-node-test-runner/-/hardhat-node-test-runner-3.0.7.tgz", - "integrity": "sha512-MwYZef9JwUl9HaA+rZDsXWEWdpeBfnW5YNB0kN9WUkEiPPoST4LGE/56+BtkmV487Mkt2yvuF0x73YppvX/ydA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nomicfoundation/hardhat-errors": "^3.0.2", - "@nomicfoundation/hardhat-node-test-reporter": "^3.0.0", - "@nomicfoundation/hardhat-utils": "^3.0.5", - "@nomicfoundation/hardhat-zod-utils": "^3.0.0", - "tsx": "^4.19.3", - "zod": "^3.23.8" - }, - "peerDependencies": { - "hardhat": "^3.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-toolbox-viem": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox-viem/-/hardhat-toolbox-viem-5.0.1.tgz", - "integrity": "sha512-NhPQjHwTk356k6WS7tPeEWS5ymtlZe2lzcZzvgd2AD7wrMXE/zUu5qacXgwPq/M6HVEczSpuFeu+/koQgA2pbA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@nomicfoundation/hardhat-ignition": "^3.0.0", - "@nomicfoundation/hardhat-ignition-viem": "^3.0.0", - "@nomicfoundation/hardhat-keystore": "^3.0.0", - "@nomicfoundation/hardhat-network-helpers": "^3.0.0", - "@nomicfoundation/hardhat-node-test-runner": "^3.0.0", - "@nomicfoundation/hardhat-verify": "^3.0.0", - "@nomicfoundation/hardhat-viem": "^3.0.0", - "@nomicfoundation/hardhat-viem-assertions": "^3.0.0", - "@nomicfoundation/ignition-core": "^3.0.0", - "hardhat": "^3.0.0", - "viem": "^2.30.0" - } - }, - "node_modules/@nomicfoundation/hardhat-utils": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-utils/-/hardhat-utils-3.0.5.tgz", - "integrity": "sha512-5zkQSuSxkwK7fQxKswJ1GGc/3AuWBSmxA7GhczTPLx28dAXQnubRU8nA48SkCkKesJq5x4TROP+XheSE2VkLUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@streamparser/json-node": "^0.0.22", - "debug": "^4.3.2", - "env-paths": "^2.2.0", - "ethereum-cryptography": "^2.2.1", - "fast-equals": "^5.0.1", - "json-stream-stringify": "^3.1.6", - "rfdc": "^1.3.1", - "undici": "^6.16.1" - } - }, - "node_modules/@nomicfoundation/hardhat-utils/node_modules/undici": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", - "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/@nomicfoundation/hardhat-verify": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-3.0.7.tgz", - "integrity": "sha512-2Px2Zldg2oRJvy7odx8hZ0lZ4yjkW8XLr6umqcKl5z36+XifKRanzd8phoLEGQ8SRBNaVsaw0EDHi9Q0QTUu3A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.8.0", - "@nomicfoundation/hardhat-errors": "^3.0.3", - "@nomicfoundation/hardhat-utils": "^3.0.5", - "@nomicfoundation/hardhat-zod-utils": "^3.0.0", - "cbor2": "^1.9.0", - "chalk": "^5.3.0", - "debug": "^4.3.2", - "semver": "^7.6.3", - "zod": "^3.23.8" - }, - "peerDependencies": { - "hardhat": "^3.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-viem": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-viem/-/hardhat-viem-3.0.1.tgz", - "integrity": "sha512-sUyi3Xn31vItf925YRgHp7x6FIFfG9B+jacWYyJ0RBi7BWCrC/aSUX4jRRmpzaZ4opLQ8KXAZdxS91Yka7AYtw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nomicfoundation/hardhat-errors": "^3.0.0", - "@nomicfoundation/hardhat-utils": "^3.0.5" - }, - "peerDependencies": { - "hardhat": "^3.0.0", - "viem": "^2.30.0" - } - }, - "node_modules/@nomicfoundation/hardhat-viem-assertions": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-viem-assertions/-/hardhat-viem-assertions-3.0.4.tgz", - "integrity": "sha512-MLImyHVuFEVNOcpJ0cjmpJ/IYj/mc2Kql6g9swX5Xtln13Gyv4jJFvoyjEalt8K9SYVTNY8kpDtVmQ2fP8BsDA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nomicfoundation/hardhat-errors": "^3.0.5", - "@nomicfoundation/hardhat-utils": "^3.0.5" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-viem": "^3.0.0", - "hardhat": "^3.0.0", - "viem": "^2.30.0" - } - }, - "node_modules/@nomicfoundation/hardhat-zod-utils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-zod-utils/-/hardhat-zod-utils-3.0.1.tgz", - "integrity": "sha512-I6/pyYiS9p2lLkzQuedr1ScMocH+ew8l233xTi+LP92gjEiviJDxselpkzgU01MUM0t6BPpfP8yMO958LDEJVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nomicfoundation/hardhat-errors": "^3.0.0", - "@nomicfoundation/hardhat-utils": "^3.0.2" - }, - "peerDependencies": { - "zod": "^3.23.8" - } - }, - "node_modules/@nomicfoundation/ignition-core": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-core/-/ignition-core-3.0.5.tgz", - "integrity": "sha512-pD3IHmePTkqwbhOaUwnSYhc9PCn2e9kFYi5nvWQFGEN3pjJ+EJeMyG0QWDJQU7beBeKNxbnb1SMPY+CBN5F+kA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ethersproject/address": "5.6.1", - "@nomicfoundation/hardhat-errors": "^3.0.2", - "@nomicfoundation/hardhat-utils": "^3.0.5", - "@nomicfoundation/solidity-analyzer": "^0.1.1", - "cbor2": "^1.9.0", - "debug": "^4.3.2", - "ethers": "^6.14.0", - "immer": "10.0.2", - "lodash-es": "4.17.21", - "ndjson": "2.0.0" - } - }, - "node_modules/@nomicfoundation/ignition-core/node_modules/@ethersproject/address": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.1.tgz", - "integrity": "sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bignumber": "^5.6.2", - "@ethersproject/bytes": "^5.6.1", - "@ethersproject/keccak256": "^5.6.1", - "@ethersproject/logger": "^5.6.0", - "@ethersproject/rlp": "^5.6.1" - } - }, - "node_modules/@nomicfoundation/ignition-ui": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-3.0.5.tgz", - "integrity": "sha512-lRvbvW8hCTGs2J8w71PJRfjfs3KwVrCQ/kvmCMq3sOS7yorVYqxUw36ERvjFa1pEwc+gER/xoUycRBhfRZ3NAA==", - "dev": true - }, - "node_modules/@nomicfoundation/solidity-analyzer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", - "integrity": "sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - }, - "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" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.2.tgz", - "integrity": "sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.2.tgz", - "integrity": "sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.2.tgz", - "integrity": "sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.2.tgz", - "integrity": "sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.2.tgz", - "integrity": "sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.2.tgz", - "integrity": "sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.2.tgz", - "integrity": "sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@scure/base": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", - "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", - "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.4.0", - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", - "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@sentry/core": { - "version": "9.47.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.47.1.tgz", - "integrity": "sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@streamparser/json": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.22.tgz", - "integrity": "sha512-b6gTSBjJ8G8SuO3Gbbj+zXbVx8NSs1EbpbMKpzGLWMdkR+98McH9bEjSz3+0mPJf68c5nxa3CrJHp5EQNXM6zQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@streamparser/json-node": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@streamparser/json-node/-/json-node-0.0.22.tgz", - "integrity": "sha512-sJT2ptNRwqB1lIsQrQlCoWk5rF4tif9wDh+7yluAGijJamAhrHGYpFB/Zg3hJeceoZypi74ftXk8DHzwYpbZSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@streamparser/json": "^0.0.22" - } - }, - "node_modules/@types/node": { - "version": "22.19.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", - "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/abitype": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", - "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3.22.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.3.0" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/bn.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", - "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/cbor2": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/cbor2/-/cbor2-1.12.0.tgz", - "integrity": "sha512-3Cco8XQhi27DogSp9Ri6LYNZLi/TBY/JVnDe+mj06NkBjW/ZYOtekaEU4wZ4xcRMNrFkDv8KNtOAqHyDfz3lYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.7" - } - }, - "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, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "dev": true, - "license": "MIT", - "peer": true, - "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/elliptic/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==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/ethereum-cryptography": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", - "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "1.4.2", - "@noble/hashes": "1.4.0", - "@scure/bip32": "1.4.0", - "@scure/bip39": "1.3.0" - } - }, - "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz", - "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "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" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers/node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/ethers/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-equals": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", - "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/forge-std": { - "version": "1.9.4", - "resolved": "git+ssh://git@github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262", - "dev": true, - "license": "(Apache-2.0 OR MIT)" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/hardhat": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-3.0.15.tgz", - "integrity": "sha512-cXxaeSxFJ+u0MfbvWsS3Gdr7/uP7wjo4xviYcGdu9AKtwY6YsU+v0quK/j1NWmvO1Y4gk350SdZzQw++hJy4LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nomicfoundation/edr": "0.12.0-next.14", - "@nomicfoundation/hardhat-errors": "^3.0.4", - "@nomicfoundation/hardhat-utils": "^3.0.5", - "@nomicfoundation/hardhat-zod-utils": "^3.0.1", - "@nomicfoundation/solidity-analyzer": "^0.1.1", - "@sentry/core": "^9.4.0", - "adm-zip": "^0.4.16", - "chalk": "^5.3.0", - "chokidar": "^4.0.3", - "debug": "^4.3.2", - "enquirer": "^2.3.0", - "ethereum-cryptography": "^2.2.1", - "micro-eth-signer": "^0.14.0", - "p-map": "^7.0.2", - "resolve.exports": "^2.0.3", - "semver": "^7.6.3", - "tsx": "^4.19.3", - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "bin": { - "hardhat": "dist/src/cli.js" - } - }, - "node_modules/hardhat/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/immer": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz", - "integrity": "sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stream-stringify": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.6.tgz", - "integrity": "sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=7.10.1" - } - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "dev": true, - "license": "MIT" - }, - "node_modules/micro-eth-signer": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.14.0.tgz", - "integrity": "sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "micro-packed": "~0.7.2" - } - }, - "node_modules/micro-eth-signer/node_modules/@noble/curves": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.2" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/micro-eth-signer/node_modules/@noble/hashes": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/micro-packed": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz", - "integrity": "sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/micro-packed/node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ndjson": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz", - "integrity": "sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "json-stringify-safe": "^5.0.1", - "minimist": "^1.2.5", - "readable-stream": "^3.6.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "ndjson": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ox": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz", - "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.11.0", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "1.9.1", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "abitype": "^1.0.9", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ox/node_modules/@adraffy/ens-normalize": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", - "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/ox/node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ox/node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ox/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ox/node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ox/node_modules/@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ox/node_modules/@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "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, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/viem": { - "version": "2.39.3", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.39.3.tgz", - "integrity": "sha512-s11rPQRvUEdc5qHK3xT4fIk4qvgPAaLwaTFq+EbFlcJJD+Xn3R4mc9H6B6fquEiHl/mdsdbG/uKCnYpoNtHNHw==", - "dev": true, - "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", - "isows": "1.0.7", - "ox": "0.9.6", - "ws": "8.18.3" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/viem/node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } + "name": "hardhat-node-test", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hardhat-node-test", + "version": "1.0.0", + "dependencies": { + "@openzeppelin/contracts": "^5.4.0" + }, + "devDependencies": { + "@nomicfoundation/hardhat-ignition": "^3.0.5", + "@nomicfoundation/hardhat-toolbox-viem": "^5.0.1", + "@types/node": "^22.19.1", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.0.15", + "typescript": "~5.8.0", + "viem": "^2.39.3" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", + "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", + "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", + "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", + "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", + "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.8.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", + "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", + "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", + "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", + "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", + "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "bn.js": "^5.2.1", + "elliptic": "6.6.1", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", + "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", + "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nomicfoundation/edr": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.12.0-next.14.tgz", + "integrity": "sha512-MGHY2x7JaNdkqlQxFBYoM7Miw2EqsQrI3ReVZMwLP5mULSRTAOnt3hCw6cnjXxGi991HnejNAedJofke6OdqqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/edr-darwin-arm64": "0.12.0-next.14", + "@nomicfoundation/edr-darwin-x64": "0.12.0-next.14", + "@nomicfoundation/edr-linux-arm64-gnu": "0.12.0-next.14", + "@nomicfoundation/edr-linux-arm64-musl": "0.12.0-next.14", + "@nomicfoundation/edr-linux-x64-gnu": "0.12.0-next.14", + "@nomicfoundation/edr-linux-x64-musl": "0.12.0-next.14", + "@nomicfoundation/edr-win32-x64-msvc": "0.12.0-next.14" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-darwin-arm64": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.12.0-next.14.tgz", + "integrity": "sha512-sl0DibKSUOS7JXhUtaQ6FJUY+nk+uq5gx+Fyd9iiqs8awZPNn6KSuvV1EbWCi+yd3mrxgZ/wO8E77C1Dxj4xQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-darwin-x64": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.12.0-next.14.tgz", + "integrity": "sha512-lfmatc1MSOaw0rDFB+ynnAGz5TWm3hSeY/+zDpPZghMODZelXm4JCqF41CQ6paLsW3X/pXcHM1HUGCUBWeoI/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.12.0-next.14.tgz", + "integrity": "sha512-sWun3PhVgat8d4lg1d5MAXSIsFlSMBzvrpMSDFNOU9hPJEclSHbHBMRcarQuGqwm/5ZBzTwCS25u78A+UATTrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-musl": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.12.0-next.14.tgz", + "integrity": "sha512-omWKioD8fFp7ayCeSDu2CqvG78+oYw8zdVECDwZVmE0jpszRCsTufNYflWRQnlGqH6GqjEUwq2c3yLxFgOTjFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-gnu": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.12.0-next.14.tgz", + "integrity": "sha512-vk0s4SaC7s1wa98W24a4zqunTK/yIcSEnsSLRM/Nl+JJs6iqS8tvmnh/BbFINORMBJ065OWc10qw2Lsbu/rxtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-musl": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.12.0-next.14.tgz", + "integrity": "sha512-/xKQD6c2RXQBIb30iTeh/NrMdYvHs6Nd+2UXS6wxlfX7GzRPOkpVDiDGD7Sda82JI459KH67dADOD6CpX8cpHQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-win32-x64-msvc": { + "version": "0.12.0-next.14", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.12.0-next.14.tgz", + "integrity": "sha512-GZcyGdOoLWnUtfPU+6B1vUi4fwf3bouSRf3xuKFHz3p/WNhpDK+8Esq3UmOmYAZWRgFT0ZR6XUk9H2owGDTVvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/hardhat-errors": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-errors/-/hardhat-errors-3.0.5.tgz", + "integrity": "sha512-8Ayqf6hFM1glmrSxrXgX6n2pn5uTlHNxEB8N5Me0DOeOGB67PRIrQdiO+RzUhrNW5YgWUNWBevOLQbW06uQ79g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/hardhat-utils": "^3.0.1" + } + }, + "node_modules/@nomicfoundation/hardhat-ignition": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition/-/hardhat-ignition-3.0.5.tgz", + "integrity": "sha512-ZTaGHdDDuHE5MjlRaSbSIOuTSZcJtVd/ShEyE6E1PEt0FLTcgYZu+NNCWRc2JZG6/Cix0PAO297y+yi+USNilA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.2", + "@nomicfoundation/hardhat-utils": "^3.0.5", + "@nomicfoundation/ignition-core": "^3.0.5", + "@nomicfoundation/ignition-ui": "^3.0.5", + "chalk": "^5.3.0", + "debug": "^4.3.2", + "json5": "^2.2.3", + "prompts": "^2.4.2" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-verify": "^3.0.0", + "hardhat": "^3.0.0" + } + }, + "node_modules/@nomicfoundation/hardhat-ignition-viem": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition-viem/-/hardhat-ignition-viem-3.0.5.tgz", + "integrity": "sha512-bK+Rmg7m6wJxoqXOTxl/HA6AakdS6OUJjGUUYxfFpuXCH9c4tW3Qp7fpgAhI0W3cCPAeT42OANSDlCH/MRZbBA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.2" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-ignition": "^3.0.5", + "@nomicfoundation/hardhat-verify": "^3.0.0", + "@nomicfoundation/hardhat-viem": "^3.0.0", + "@nomicfoundation/ignition-core": "^3.0.5", + "hardhat": "^3.0.0", + "viem": "^2.30.0" + } + }, + "node_modules/@nomicfoundation/hardhat-keystore": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-keystore/-/hardhat-keystore-3.0.3.tgz", + "integrity": "sha512-rkwfdy/GsX/2SV49RGBvMsCuR+SYGJQGD3wcrS5m2Cyap5eQFEgKZbqpua6YQRA2raxRmVVH6antIIftgBFXAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/ciphers": "1.2.1", + "@noble/hashes": "1.7.1", + "@nomicfoundation/hardhat-errors": "^3.0.0", + "@nomicfoundation/hardhat-utils": "^3.0.5", + "@nomicfoundation/hardhat-zod-utils": "^3.0.0", + "chalk": "^5.3.0", + "debug": "^4.3.2", + "zod": "^3.23.8" + }, + "peerDependencies": { + "hardhat": "^3.0.0" + } + }, + "node_modules/@nomicfoundation/hardhat-network-helpers": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-3.0.3.tgz", + "integrity": "sha512-FqXD8CPFNdluEhELqNV/Q0grOQtlwRWr28LW+/NTas3rrDAXpNOIPCCq3RIXJIqsdbNPQsG2FpnfKj9myqIsKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.5", + "@nomicfoundation/hardhat-utils": "^3.0.5" + }, + "peerDependencies": { + "hardhat": "^3.0.0" + } + }, + "node_modules/@nomicfoundation/hardhat-node-test-reporter": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-node-test-reporter/-/hardhat-node-test-reporter-3.0.1.tgz", + "integrity": "sha512-p6yNKZFnJ2OMplXx7zi45KGWr4hr/qMkg+gTuSSLLlph7NL1DGjGG+N6GrZs46AGSrsnYEocKXGnavl92dxEig==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@actions/core": "^1.10.1", + "chalk": "^5.3.0", + "jest-diff": "^29.7.0" + } + }, + "node_modules/@nomicfoundation/hardhat-node-test-runner": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-node-test-runner/-/hardhat-node-test-runner-3.0.7.tgz", + "integrity": "sha512-MwYZef9JwUl9HaA+rZDsXWEWdpeBfnW5YNB0kN9WUkEiPPoST4LGE/56+BtkmV487Mkt2yvuF0x73YppvX/ydA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.2", + "@nomicfoundation/hardhat-node-test-reporter": "^3.0.0", + "@nomicfoundation/hardhat-utils": "^3.0.5", + "@nomicfoundation/hardhat-zod-utils": "^3.0.0", + "tsx": "^4.19.3", + "zod": "^3.23.8" + }, + "peerDependencies": { + "hardhat": "^3.0.0" + } + }, + "node_modules/@nomicfoundation/hardhat-toolbox-viem": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox-viem/-/hardhat-toolbox-viem-5.0.1.tgz", + "integrity": "sha512-NhPQjHwTk356k6WS7tPeEWS5ymtlZe2lzcZzvgd2AD7wrMXE/zUu5qacXgwPq/M6HVEczSpuFeu+/koQgA2pbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@nomicfoundation/hardhat-ignition": "^3.0.0", + "@nomicfoundation/hardhat-ignition-viem": "^3.0.0", + "@nomicfoundation/hardhat-keystore": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^3.0.0", + "@nomicfoundation/hardhat-node-test-runner": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^3.0.0", + "@nomicfoundation/hardhat-viem": "^3.0.0", + "@nomicfoundation/hardhat-viem-assertions": "^3.0.0", + "@nomicfoundation/ignition-core": "^3.0.0", + "hardhat": "^3.0.0", + "viem": "^2.30.0" + } + }, + "node_modules/@nomicfoundation/hardhat-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-utils/-/hardhat-utils-3.0.5.tgz", + "integrity": "sha512-5zkQSuSxkwK7fQxKswJ1GGc/3AuWBSmxA7GhczTPLx28dAXQnubRU8nA48SkCkKesJq5x4TROP+XheSE2VkLUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@streamparser/json-node": "^0.0.22", + "debug": "^4.3.2", + "env-paths": "^2.2.0", + "ethereum-cryptography": "^2.2.1", + "fast-equals": "^5.0.1", + "json-stream-stringify": "^3.1.6", + "rfdc": "^1.3.1", + "undici": "^6.16.1" + } + }, + "node_modules/@nomicfoundation/hardhat-utils/node_modules/undici": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@nomicfoundation/hardhat-verify": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-3.0.7.tgz", + "integrity": "sha512-2Px2Zldg2oRJvy7odx8hZ0lZ4yjkW8XLr6umqcKl5z36+XifKRanzd8phoLEGQ8SRBNaVsaw0EDHi9Q0QTUu3A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@nomicfoundation/hardhat-errors": "^3.0.3", + "@nomicfoundation/hardhat-utils": "^3.0.5", + "@nomicfoundation/hardhat-zod-utils": "^3.0.0", + "cbor2": "^1.9.0", + "chalk": "^5.3.0", + "debug": "^4.3.2", + "semver": "^7.6.3", + "zod": "^3.23.8" + }, + "peerDependencies": { + "hardhat": "^3.0.0" + } + }, + "node_modules/@nomicfoundation/hardhat-viem": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-viem/-/hardhat-viem-3.0.1.tgz", + "integrity": "sha512-sUyi3Xn31vItf925YRgHp7x6FIFfG9B+jacWYyJ0RBi7BWCrC/aSUX4jRRmpzaZ4opLQ8KXAZdxS91Yka7AYtw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.0", + "@nomicfoundation/hardhat-utils": "^3.0.5" + }, + "peerDependencies": { + "hardhat": "^3.0.0", + "viem": "^2.30.0" + } + }, + "node_modules/@nomicfoundation/hardhat-viem-assertions": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-viem-assertions/-/hardhat-viem-assertions-3.0.4.tgz", + "integrity": "sha512-MLImyHVuFEVNOcpJ0cjmpJ/IYj/mc2Kql6g9swX5Xtln13Gyv4jJFvoyjEalt8K9SYVTNY8kpDtVmQ2fP8BsDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.5", + "@nomicfoundation/hardhat-utils": "^3.0.5" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-viem": "^3.0.0", + "hardhat": "^3.0.0", + "viem": "^2.30.0" + } + }, + "node_modules/@nomicfoundation/hardhat-zod-utils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-zod-utils/-/hardhat-zod-utils-3.0.1.tgz", + "integrity": "sha512-I6/pyYiS9p2lLkzQuedr1ScMocH+ew8l233xTi+LP92gjEiviJDxselpkzgU01MUM0t6BPpfP8yMO958LDEJVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.0", + "@nomicfoundation/hardhat-utils": "^3.0.2" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@nomicfoundation/ignition-core": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-core/-/ignition-core-3.0.5.tgz", + "integrity": "sha512-pD3IHmePTkqwbhOaUwnSYhc9PCn2e9kFYi5nvWQFGEN3pjJ+EJeMyG0QWDJQU7beBeKNxbnb1SMPY+CBN5F+kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ethersproject/address": "5.6.1", + "@nomicfoundation/hardhat-errors": "^3.0.2", + "@nomicfoundation/hardhat-utils": "^3.0.5", + "@nomicfoundation/solidity-analyzer": "^0.1.1", + "cbor2": "^1.9.0", + "debug": "^4.3.2", + "ethers": "^6.14.0", + "immer": "10.0.2", + "lodash-es": "4.17.21", + "ndjson": "2.0.0" + } + }, + "node_modules/@nomicfoundation/ignition-core/node_modules/@ethersproject/address": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.1.tgz", + "integrity": "sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.6.2", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/keccak256": "^5.6.1", + "@ethersproject/logger": "^5.6.0", + "@ethersproject/rlp": "^5.6.1" + } + }, + "node_modules/@nomicfoundation/ignition-ui": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-3.0.5.tgz", + "integrity": "sha512-lRvbvW8hCTGs2J8w71PJRfjfs3KwVrCQ/kvmCMq3sOS7yorVYqxUw36ERvjFa1pEwc+gER/xoUycRBhfRZ3NAA==", + "dev": true + }, + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", + "integrity": "sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + }, + "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" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.2.tgz", + "integrity": "sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.2.tgz", + "integrity": "sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.2.tgz", + "integrity": "sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.2.tgz", + "integrity": "sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.2.tgz", + "integrity": "sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.2.tgz", + "integrity": "sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.2.tgz", + "integrity": "sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@openzeppelin/contracts": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz", + "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==", + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sentry/core": { + "version": "9.47.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.47.1.tgz", + "integrity": "sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@streamparser/json": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.22.tgz", + "integrity": "sha512-b6gTSBjJ8G8SuO3Gbbj+zXbVx8NSs1EbpbMKpzGLWMdkR+98McH9bEjSz3+0mPJf68c5nxa3CrJHp5EQNXM6zQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@streamparser/json-node": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@streamparser/json-node/-/json-node-0.0.22.tgz", + "integrity": "sha512-sJT2ptNRwqB1lIsQrQlCoWk5rF4tif9wDh+7yluAGijJamAhrHGYpFB/Zg3hJeceoZypi74ftXk8DHzwYpbZSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@streamparser/json": "^0.0.22" + } + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/abitype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", + "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/cbor2": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/cbor2/-/cbor2-1.12.0.tgz", + "integrity": "sha512-3Cco8XQhi27DogSp9Ri6LYNZLi/TBY/JVnDe+mj06NkBjW/ZYOtekaEU4wZ4xcRMNrFkDv8KNtOAqHyDfz3lYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.7" + } + }, + "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, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "peer": true, + "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/elliptic/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==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz", + "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "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" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/ethers/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/forge-std": { + "version": "1.9.4", + "resolved": "git+ssh://git@github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262", + "dev": true, + "license": "(Apache-2.0 OR MIT)" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/hardhat": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-3.0.15.tgz", + "integrity": "sha512-cXxaeSxFJ+u0MfbvWsS3Gdr7/uP7wjo4xviYcGdu9AKtwY6YsU+v0quK/j1NWmvO1Y4gk350SdZzQw++hJy4LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/edr": "0.12.0-next.14", + "@nomicfoundation/hardhat-errors": "^3.0.4", + "@nomicfoundation/hardhat-utils": "^3.0.5", + "@nomicfoundation/hardhat-zod-utils": "^3.0.1", + "@nomicfoundation/solidity-analyzer": "^0.1.1", + "@sentry/core": "^9.4.0", + "adm-zip": "^0.4.16", + "chalk": "^5.3.0", + "chokidar": "^4.0.3", + "debug": "^4.3.2", + "enquirer": "^2.3.0", + "ethereum-cryptography": "^2.2.1", + "micro-eth-signer": "^0.14.0", + "p-map": "^7.0.2", + "resolve.exports": "^2.0.3", + "semver": "^7.6.3", + "tsx": "^4.19.3", + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "bin": { + "hardhat": "dist/src/cli.js" + } + }, + "node_modules/hardhat/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/immer": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz", + "integrity": "sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stream-stringify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.6.tgz", + "integrity": "sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=7.10.1" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/micro-eth-signer": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.14.0.tgz", + "integrity": "sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "micro-packed": "~0.7.2" + } + }, + "node_modules/micro-eth-signer/node_modules/@noble/curves": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.2" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-eth-signer/node_modules/@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-packed": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz", + "integrity": "sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-packed/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ndjson": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz", + "integrity": "sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "json-stringify-safe": "^5.0.1", + "minimist": "^1.2.5", + "readable-stream": "^3.6.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "ndjson": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ox": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz", + "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ox/node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "license": "ISC", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.39.3", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.39.3.tgz", + "integrity": "sha512-s11rPQRvUEdc5qHK3xT4fIk4qvgPAaLwaTFq+EbFlcJJD+Xn3R4mc9H6B6fquEiHl/mdsdbG/uKCnYpoNtHNHw==", + "dev": true, + "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", + "isows": "1.0.7", + "ox": "0.9.6", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } } diff --git a/hardhat-node-test/package.json b/hardhat-node-test/package.json index d853ec8..a334806 100644 --- a/hardhat-node-test/package.json +++ b/hardhat-node-test/package.json @@ -13,5 +13,8 @@ "hardhat": "^3.0.15", "typescript": "~5.8.0", "viem": "^2.39.3" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.4.0" } } diff --git a/hardhat-node-test/test/OpenScanPayment.ts b/hardhat-node-test/test/OpenScanPayment.ts new file mode 100644 index 0000000..0404d9a --- /dev/null +++ b/hardhat-node-test/test/OpenScanPayment.ts @@ -0,0 +1,142 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { network } from "hardhat"; +import { parseEther, decodeFunctionData, getAddress } from "viem"; + +describe("OpenScanPayment", async function () { + const { viem } = await network.connect(); + const publicClient = await viem.getPublicClient(); + const [deployer, payer] = await viem.getWalletClients(); + + it("Should deploy with deployer as owner", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + const owner = await payment.read.owner(); + assert.equal(owner.toLowerCase(), deployer.account.address.toLowerCase()); + }); + + it("Should emit PaymentReceived event on pay()", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + const amount = parseEther("1"); + + await viem.assertions.emitWithArgs( + payment.write.pay({ value: amount, account: payer.account }), + payment, + "PaymentReceived", + [getAddress(payer.account.address), amount], + ); + }); + + it("Should revert pay() with zero value", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + + await assert.rejects( + payment.write.pay({ value: 0n, account: payer.account }), + /payment must be greater than zero/, + ); + }); + + it("Should forward ETH to owner on pay()", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + const amount = parseEther("1"); + + const ownerBalanceBefore = await publicClient.getBalance({ + address: deployer.account.address, + }); + + await payment.write.pay({ value: amount, account: payer.account }); + + const ownerBalanceAfter = await publicClient.getBalance({ + address: deployer.account.address, + }); + + assert.equal(ownerBalanceAfter - ownerBalanceBefore, amount); + }); + + it("Should emit DonationReceived event on donate()", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + const amount = parseEther("0.5"); + const message = "Thanks for OpenScan!"; + + await viem.assertions.emitWithArgs( + payment.write.donate([message], { value: amount, account: payer.account }), + payment, + "DonationReceived", + [getAddress(payer.account.address), amount], + ); + }); + + it("Should revert donate() with zero value", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + + await assert.rejects( + payment.write.donate(["Hello"], { value: 0n, account: payer.account }), + /donation must be greater than zero/, + ); + }); + + it("Should store message in calldata on donate()", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + const amount = parseEther("0.1"); + const message = "Hello from donor!"; + + const txHash = await payment.write.donate([message], { + value: amount, + account: payer.account, + }); + + const tx = await publicClient.getTransaction({ hash: txHash }); + + // Decode the calldata to extract the message + const decoded = decodeFunctionData({ + abi: payment.abi, + data: tx.input, + }); + + assert.equal(decoded.functionName, "donate"); + assert.equal((decoded.args as [string])[0], message); + }); + + it("Should allow owner to executeCall", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + + // Deploy a counter to test executeCall + const counter = await viem.deployContract("Counter"); + const initialValue = await counter.read.x(); + + // Encode the inc() call + const { encodeFunctionData } = await import("viem"); + const callData = encodeFunctionData({ + abi: counter.abi, + functionName: "inc", + args: [], + }); + + // Execute via payment contract + await payment.write.executeCall([counter.address, callData], { + account: deployer.account, + }); + + const newValue = await counter.read.x(); + assert.equal(newValue, initialValue + 1n); + }); + + it("Should revert executeCall for non-owner", async function () { + const payment = await viem.deployContract("OpenScanPayment", [deployer.account.address]); + const counter = await viem.deployContract("Counter"); + + const { encodeFunctionData } = await import("viem"); + const callData = encodeFunctionData({ + abi: counter.abi, + functionName: "inc", + args: [], + }); + + await assert.rejects( + payment.write.executeCall([counter.address, callData], { + account: payer.account, + }), + /OwnableUnauthorizedAccount/, + ); + }); +}); diff --git a/package-lock.json b/package-lock.json index d6933d7..8b878b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,11 @@ "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^13.5.0", "ethers": "^6.14.4", + "explorer-network-connectors": "^0.1.0", "jszip": "^3.10.1", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-markdown": "^10.1.0", "react-router-dom": "^7.4.1", "vitest": "^4.0.14", "wagmi": "^2.15.6", @@ -27,6 +29,7 @@ "@babel/preset-env": "^7.23.1", "@babel/preset-react": "^7.22.15", "@biomejs/biome": "2.3.7", + "@playwright/test": "^1.57.0", "@svgr/webpack": "^8.1.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", @@ -43,6 +46,15 @@ "webpack-dev-server": "^4.15.1" } }, + "../explorer-network-connectors": { + "version": "0.0.1", + "extraneous": true, + "devDependencies": { + "@tsconfig/node24": "^24.0.3", + "@types/node": "^24.10.0", + "typescript": "^5.9.3" + } + }, "node_modules/@acemir/cssom": { "version": "0.9.24", "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.24.tgz", @@ -182,6 +194,7 @@ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1826,7 +1839,6 @@ "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.7.tgz", "integrity": "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw==", "dev": true, - "license": "MIT OR Apache-2.0", "bin": { "biome": "bin/biome" }, @@ -2072,6 +2084,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2115,6 +2128,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3104,6 +3118,7 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -3145,6 +3160,22 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rainbow-me/rainbowkit": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/@rainbow-me/rainbowkit/-/rainbowkit-2.2.8.tgz", @@ -3616,6 +3647,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -4261,6 +4293,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -4684,6 +4717,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -5232,6 +5266,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -5365,7 +5400,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.0.tgz", "integrity": "sha512-lFsd8NBhkh26vzaq8uOOM2o3r4NtJros7kASI+TTMBmKT8C43HVN/2mOI9PFX9kzmxLThBzOLa4NI8ORhX8N9g==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -5393,6 +5427,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5600,6 +5635,15 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/express": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", @@ -5639,6 +5683,15 @@ "@types/send": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -5670,6 +5723,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -5689,6 +5751,7 @@ "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.8.0" } @@ -5721,8 +5784,8 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -5733,6 +5796,7 @@ "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -5793,6 +5857,18 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@vanilla-extract/css": { "version": "1.17.3", "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.17.3.tgz", @@ -6337,6 +6413,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -6358,6 +6435,7 @@ "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.17.3.tgz", "integrity": "sha512-fgZR9fAiCFtGaosTspkTx5lidccq9Z5xRWOk1HG0VfB6euQGw2//Db7upiP4uQ7DPst2YS9yQN2A1m9+iJLYCw==", "license": "MIT", + "peer": true, "dependencies": { "eventemitter3": "5.0.1", "mipd": "0.0.7", @@ -6890,6 +6968,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7107,6 +7186,16 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -7299,6 +7388,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", @@ -7358,6 +7448,7 @@ "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -7491,6 +7582,16 @@ "sha.js": "^2.4.11" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", @@ -7516,6 +7617,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -7614,6 +7755,16 @@ "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -7850,6 +8001,7 @@ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", "license": "MIT", + "peer": true, "dependencies": { "node-fetch": "^2.7.0" } @@ -8191,6 +8343,19 @@ "devOptional": true, "license": "MIT" }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -8339,6 +8504,19 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -8511,6 +8689,7 @@ "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.15.tgz", "integrity": "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==", "license": "MIT", + "peer": true, "dependencies": { "@ecies/ciphers": "^0.2.3", "@noble/ciphers": "^1.3.0", @@ -8853,6 +9032,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -9061,7 +9250,8 @@ "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/eventemitter3": { "version": "5.0.1", @@ -9111,6 +9301,11 @@ "node": ">=12.0.0" } }, + "node_modules/explorer-network-connectors": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/explorer-network-connectors/-/explorer-network-connectors-0.1.0.tgz", + "integrity": "sha512-QrlExGXXZR9Zf9h58ZO2MDouMdjIGEHBnxo6h/EmPFGOXZ727kTTkqThCMEnlx2B5P7DzYcEZg9oxdE7EpQPbg==" + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -9185,6 +9380,12 @@ "dev": true, "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/extension-port-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", @@ -9687,6 +9888,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -9795,6 +10036,16 @@ "node": ">=12" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", @@ -9994,7 +10245,8 @@ "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" + "license": "Apache-2.0", + "peer": true }, "node_modules/ieee754": { "version": "1.2.1", @@ -10175,6 +10427,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -10204,6 +10462,30 @@ "url": "https://github.com/sponsors/brc-dd" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -10268,6 +10550,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -10334,6 +10626,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10529,6 +10831,7 @@ "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.23", "@asamuzakjp/dom-selector": "^6.7.4", @@ -10826,12 +11129,21 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -10886,6 +11198,159 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -10958,14 +11423,456 @@ "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", "license": "MIT" }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -11599,6 +12506,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -11806,6 +12738,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -11852,6 +12831,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -12004,6 +12984,16 @@ "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", "license": "MIT" }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -12032,8 +13022,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz", "integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/pump": { "version": "3.0.3", @@ -12172,6 +13161,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12181,6 +13171,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -12194,6 +13185,33 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", @@ -12306,6 +13324,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -12444,6 +13463,39 @@ "node": ">= 0.10" } }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -13098,6 +14150,7 @@ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", "license": "MIT", + "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -13206,6 +14259,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -13316,6 +14379,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -13367,6 +14444,24 @@ "webpack": "^5.27.0" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/superstruct": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.4.tgz", @@ -13543,6 +14638,7 @@ "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "devOptional": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.14.0", @@ -13664,6 +14760,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -13742,6 +14839,26 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-loader": { "version": "9.5.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", @@ -13919,6 +15036,105 @@ "node": ">=4" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -14106,6 +15322,7 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -14116,6 +15333,7 @@ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -14173,7 +15391,6 @@ "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.11.2.tgz", "integrity": "sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==", "license": "MIT", - "peer": true, "dependencies": { "proxy-compare": "2.5.1", "use-sync-external-store": "1.2.0" @@ -14199,7 +15416,6 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -14214,6 +15430,34 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/viem": { "version": "2.31.4", "resolved": "https://registry.npmjs.org/viem/-/viem-2.31.4.tgz", @@ -14225,6 +15469,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@noble/curves": "1.9.2", "@noble/hashes": "1.8.0", @@ -14333,6 +15578,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -14424,6 +15670,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14538,6 +15785,7 @@ "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.15.6.tgz", "integrity": "sha512-tR4tm+7eE0UloQe1oi4hUIjIDyjv5ImQlzq/QcvvfJYWF/EquTfGrmht6+nTYGCIeSzeEvbK90KgWyNqa+HD7Q==", "license": "MIT", + "peer": true, "dependencies": { "@wagmi/connectors": "5.8.5", "@wagmi/core": "2.17.3", @@ -14606,6 +15854,7 @@ "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -14654,6 +15903,7 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -15023,6 +16273,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -15223,6 +16474,16 @@ "optional": true } } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 4d1e7bc..e4c1064 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^13.5.0", "ethers": "^6.14.4", + "explorer-network-connectors": "^0.1.0", "jszip": "^3.10.1", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-markdown": "^10.1.0", "react-router-dom": "^7.4.1", "vitest": "^4.0.14", "wagmi": "^2.15.6", @@ -19,12 +21,20 @@ }, "scripts": { "start": "NODE_ENV=development webpack serve --config webpack.config.js --mode development", - "build": "NODE_ENV=production webpack --config webpack.config.js --mode production", - "test": "npx vitest", + "build:production": "bash ./scripts/build-production.sh", + "build:staging": "bash ./scripts/build-staging.sh", + "test": "npx vitest", "test:run": "npx vitest run", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:debug": "playwright test --debug", "dev": "bash scripts/run-test-env.sh", "typecheck": "tsc --noEmit", - "format:fix": "npx @biomejs/biome format --write" + "format": "npx @biomejs/biome format", + "format:fix": "npx @biomejs/biome format --write", + "lint": "npx @biomejs/biome lint", + "lint:fix": "npx @biomejs/biome lint --write --max-diagnostics 1024", + "check": "npx @biomejs/biome check --max-diagnostics 1024" }, "eslintConfig": { "extends": [ @@ -49,6 +59,7 @@ "@babel/preset-env": "^7.23.1", "@babel/preset-react": "^7.22.15", "@biomejs/biome": "2.3.7", + "@playwright/test": "^1.57.0", "@svgr/webpack": "^8.1.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..40caa93 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,24 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./e2e/tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 3 : 1, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + timeout: 60000, + use: { + baseURL: "http://localhost:3030", + trace: "on-first-retry", + screenshot: "only-on-failure", + headless: true, + }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], + webServer: { + command: "npm start", + url: "http://localhost:3030", + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, +}); diff --git a/public/index.html b/public/index.html index 6c3b021..1c920f2 100644 --- a/public/index.html +++ b/public/index.html @@ -9,13 +9,13 @@ content="" /> - + - - + + Openscan diff --git a/scripts/build-production.sh b/scripts/build-production.sh index 22db4e3..2d32298 100755 --- a/scripts/build-production.sh +++ b/scripts/build-production.sh @@ -23,7 +23,7 @@ rm -r dist || true # Build the app echo "Building React app on commit $COMMIT_HASH" -NODE_ENV=production REACT_APP_COMMIT_HASH=$COMMIT_HASH npm run build +NODE_ENV=production REACT_APP_COMMIT_HASH=$COMMIT_HASH webpack --config webpack.config.js --mode production echo "Production build completed!" echo "Build output is in ./dist/" diff --git a/scripts/build-staging.sh b/scripts/build-staging.sh index 53e38dc..f263500 100755 --- a/scripts/build-staging.sh +++ b/scripts/build-staging.sh @@ -16,7 +16,6 @@ COMMIT_HASH=$(git rev-parse HEAD) # Build the app echo "Building React app on commit $COMMIT_HASH" -NODE_ENV=staging REACT_APP_COMMIT_HASH=$COMMIT_HASH npm run build - +NODE_ENV=staging REACT_APP_COMMIT_HASH=$COMMIT_HASH webpack --config webpack.config.js --mode production echo "Staging build completed!" echo "Build output is in ./dist/" \ No newline at end of file diff --git a/scripts/run-test-env.sh b/scripts/run-test-env.sh index 69b5e05..eeaf2b7 100755 --- a/scripts/run-test-env.sh +++ b/scripts/run-test-env.sh @@ -96,7 +96,7 @@ echo "✨ Test Environment Ready!" echo "================================================" echo "" echo "šŸ“ Local Test Node: http://127.0.0.1:8545 (Chain ID: 31337)" -echo "šŸ“ OpenScan: http://localhost:3000" +echo "šŸ“ OpenScan: http://localhost:3030" echo "" echo "🌐 Available Networks:" echo " - Ethereum Mainnet (Chain ID: 1)" diff --git a/src/App.tsx b/src/App.tsx index 02af886..86323fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,174 +1,211 @@ +import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useCallback, useEffect } from "react"; import { - BrowserRouter as Router, - HashRouter, - Routes, - Route, - Navigate, + HashRouter, + Navigate, + Route, + BrowserRouter as Router, + Routes, + useLocation, + useNavigate, } from "react-router-dom"; -import { useCallback } from "react"; import { WagmiProvider } from "wagmi"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -// @ts-ignore -import { - RainbowKitProvider, - darkTheme, - lightTheme, -} from "@rainbow-me/rainbowkit"; +import { cssVariables, getRainbowKitTheme } from "./theme"; import { networkConfig } from "./utils/networkConfig"; -import "./styles/rainbowkit.css"; +import { getBaseDomainUrl, getSubdomain, getSubdomainRedirect } from "./utils/subdomainUtils"; +import "@rainbow-me/rainbowkit/styles.css"; // Detect if we're running on GitHub Pages and get the correct basename function getBasename(): string { - const { hostname, pathname } = window.location; - - // Check if we're on GitHub Pages - if (hostname.includes("github.io")) { - // Extract repo name from pathname (first segment after domain) - const pathSegments = pathname.split("/").filter(Boolean); - if (pathSegments.length > 0) { - return `/${pathSegments[0]}`; - } - } - - // For local development or custom domains, no basename needed - return ""; + const { hostname, pathname } = window.location; + + // Check if we're on GitHub Pages + if (hostname.includes("github.io")) { + // Extract repo name from pathname (first segment after domain) + const pathSegments = pathname.split("/").filter(Boolean); + if (pathSegments.length > 0) { + return `/${pathSegments[0]}`; + } + } + + // For local development or custom domains, no basename needed + return ""; } -import Navbar from "./components/common/Navbar"; -import Footer from "./components/common/Footer"; -import NotificationDisplay from "./components/common/NotificationDisplay"; + import ErrorBoundary from "./components/common/ErrorBoundary"; +import Footer from "./components/common/Footer"; import { IsometricBlocks } from "./components/common/IsometricBlocks"; +import NotificationDisplay from "./components/common/NotificationDisplay"; +import Navbar from "./components/navbar"; +import "./styles/base.css"; import "./styles/styles.css"; import "./styles/layouts.css"; import "./styles/components.css"; import "./styles/tables.css"; import "./styles/forms.css"; -import { useAppReady, useOnAppReady } from "./hooks/useAppReady"; import Loading from "./components/common/Loading"; import { - LazyHome, - LazyChain, - LazyBlocks, - LazyBlock, - LazyTxs, - LazyTx, - LazyAddress, - LazyMempool, - LazySettings, - LazyDevTools, - LazyAbout, + LazyAbout, + LazyAddress, + LazyBlock, + LazyBlocks, + LazyChain, + LazyDevTools, + LazyHome, + LazyMempool, + LazyProfile, + LazySettings, + LazySubscriptions, + LazySupporters, + LazyTokenDetails, + LazyTx, + LazyTxs, } from "./components/LazyComponents"; import { NotificationProvider } from "./context/NotificationContext"; -import { - SettingsProvider, - useTheme, - useSettings, -} from "./context/SettingsContext"; +import { SettingsProvider, useSettings, useTheme } from "./context/SettingsContext"; +import { useAppReady, useOnAppReady } from "./hooks/useAppReady"; // Detect GH Pages once -const isGhPages = - typeof window !== "undefined" && - window.location.hostname.includes("github.io"); +const isGhPages = typeof window !== "undefined" && window.location.hostname.includes("github.io"); // Create a client for React Query const queryClient = new QueryClient(); +// Component that handles subdomain redirects +function SubdomainRedirect() { + const navigate = useNavigate(); + const location = useLocation(); + + useEffect(() => { + const subdomain = getSubdomain(); + + // If there's a subdomain, check if it's valid + if (subdomain) { + const redirectPath = getSubdomainRedirect(); + + if (redirectPath) { + // Valid subdomain - redirect to configured path (only on root) + if (location.pathname === "/") { + navigate(redirectPath, { replace: true }); + } + } else { + // Invalid subdomain - redirect to base domain + const baseDomainUrl = getBaseDomainUrl(); + if (baseDomainUrl) { + window.location.href = baseDomainUrl; + } + } + } + }, [location.pathname, navigate]); + + return null; +} + // Separate component that uses the theme context function AppContent() { - const onAppReadyCallback = useCallback(async () => {}, []); - - useOnAppReady(onAppReadyCallback); - - const { fullyReady } = useAppReady(); - const { isDarkMode } = useTheme(); // Now this is inside ThemeProvider - const { settings } = useSettings(); - - return ( - <> - {!fullyReady ? ( - - ) : ( - <> - {/* Background animated blocks - full screen (conditionally rendered) */} - {settings.showBackgroundBlocks && ( -
- -
- )} - -
- - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - -
-
- - )} - - ); + const onAppReadyCallback = useCallback(async () => {}, []); + + useOnAppReady(onAppReadyCallback); + + const { fullyReady } = useAppReady(); + const { settings } = useSettings(); + + return ( + <> + {!fullyReady ? ( + + ) : ( + <> + {/* Background animated blocks - full screen (conditionally rendered) */} + {settings.showBackgroundBlocks && ( +
+ +
+ )} + +
+ + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+
+ + )} + + ); } // Main App component that provides the theme context function App() { - const BaseRouter = isGhPages ? HashRouter : Router; - // IMPORTANT: no basename for HashRouter - const basename = isGhPages ? "" : getBasename(); - - return ( - - - - - - - - - - - - - - - - ); + const BaseRouter = isGhPages ? HashRouter : Router; + // IMPORTANT: no basename for HashRouter + const basename = isGhPages ? "" : getBasename(); + + return ( + + + + + + + + + + + + + + + + ); } // Wrapper component to use theme inside SettingsProvider -function RainbowKitProviderWrapper({ - children, -}: { - children: React.ReactNode; -}) { - const { isDarkMode } = useTheme(); - - return ( - - {children} - - ); +function RainbowKitProviderWrapper({ children }: { children: React.ReactNode }) { + const { isDarkMode } = useTheme(); + + // Inject CSS variables and set data-theme attribute + useEffect(() => { + // Inject CSS variables if not already present + const styleId = "openscan-theme-variables"; + if (!document.getElementById(styleId)) { + const style = document.createElement("style"); + style.id = styleId; + style.textContent = cssVariables; + document.head.appendChild(style); + } + + // Set data-theme attribute for CSS variable switching + document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light"); + }, [isDarkMode]); + + return {children}; } export default App; diff --git a/src/assets/arbitrum-logo.svg b/src/assets/arbitrum-logo.svg deleted file mode 100644 index dd17407..0000000 --- a/src/assets/arbitrum-logo.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/base-logo.svg b/src/assets/base-logo.svg deleted file mode 100644 index 22d8935..0000000 --- a/src/assets/base-logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/hardhat-logo.svg b/src/assets/hardhat-logo.svg deleted file mode 100644 index c7fcb1b..0000000 --- a/src/assets/hardhat-logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/assets/optimism-logo.svg b/src/assets/optimism-logo.svg deleted file mode 100644 index 1a99930..0000000 --- a/src/assets/optimism-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/paymentHelp1.png b/src/assets/paymentHelp1.png new file mode 100644 index 0000000..f1036c0 Binary files /dev/null and b/src/assets/paymentHelp1.png differ diff --git a/src/assets/paymentHelp2.png b/src/assets/paymentHelp2.png new file mode 100644 index 0000000..911d59b Binary files /dev/null and b/src/assets/paymentHelp2.png differ diff --git a/src/assets/paymentHelp3.png b/src/assets/paymentHelp3.png new file mode 100644 index 0000000..bb45cba Binary files /dev/null and b/src/assets/paymentHelp3.png differ diff --git a/src/components/LazyComponents.tsx b/src/components/LazyComponents.tsx index 1f00f29..64102e8 100644 --- a/src/components/LazyComponents.tsx +++ b/src/components/LazyComponents.tsx @@ -2,27 +2,33 @@ import { lazy, Suspense } from "react"; import Loading from "./common/Loading"; // Lazy load page components -const Home = lazy(() => import("./pages/Home")); -const Chain = lazy(() => import("./pages/Chain")); -const Blocks = lazy(() => import("./pages/Blocks")); -const Block = lazy(() => import("./pages/Block")); -const Txs = lazy(() => import("./pages/Txs")); -const Tx = lazy(() => import("./pages/Tx")); -const Address = lazy(() => import("./pages/Address")); -const Mempool = lazy(() => import("./pages/Mempool")); -const Settings = lazy(() => import("./pages/Settings")); -const DevTools = lazy(() => import("./pages/DevTools")); -const About = lazy(() => import("./pages/About")); +const Home = lazy(() => import("./pages/home")); +const Chain = lazy(() => import("./pages/network")); +const Blocks = lazy(() => import("./pages/blocks")); +const Block = lazy(() => import("./pages/block")); +const Txs = lazy(() => import("./pages/txs")); +const Tx = lazy(() => import("./pages/tx")); +const Address = lazy(() => import("./pages/address")); +const TokenDetails = lazy(() => import("./pages/tokenDetails")); +const Mempool = lazy(() => import("./pages/mempool")); +const Settings = lazy(() => import("./pages/settings")); +const DevTools = lazy(() => import("./pages/devtools")); +const About = lazy(() => import("./pages/about")); +const Subscriptions = lazy(() => import("./pages/subscriptions")); +const Profile = lazy(() => import("./pages/profile")); +const Supporters = lazy(() => import("./pages/supporters")); // Higher-order component to wrap lazy components with Suspense +// biome-ignore lint/suspicious/noExplicitAny: export const withSuspense = (Component: React.ComponentType) => { - return function SuspenseWrapper(props: any) { - return ( - }> - - - ); - }; + // biome-ignore lint/suspicious/noExplicitAny: + return function SuspenseWrapper(props: any) { + return ( + }> + + + ); + }; }; // Export lazy components wrapped with Suspense @@ -33,9 +39,13 @@ export const LazyBlock = withSuspense(Block); export const LazyTxs = withSuspense(Txs); export const LazyTx = withSuspense(Tx); export const LazyAddress = withSuspense(Address); +export const LazyTokenDetails = withSuspense(TokenDetails); export const LazyMempool = withSuspense(Mempool); export const LazySettings = withSuspense(Settings); export const LazyDevTools = withSuspense(DevTools); export const LazyAbout = withSuspense(About); +export const LazySubscriptions = withSuspense(Subscriptions); +export const LazyProfile = withSuspense(Profile); +export const LazySupporters = withSuspense(Supporters); // Default exports for backward compatibility export { Home }; diff --git a/src/components/common/AddressDisplay.tsx b/src/components/common/AddressDisplay.tsx deleted file mode 100644 index bf1e503..0000000 --- a/src/components/common/AddressDisplay.tsx +++ /dev/null @@ -1,1799 +0,0 @@ -import React, { useContext, useState, useMemo, useCallback } from "react"; -import { Link } from "react-router-dom"; -import { useSourcify } from "../../hooks/useSourcify"; -import type { - Address, - AddressTransactionsResult, - Transaction, - RPCMetadata, -} from "../../types"; -import { AppContext } from "../../context"; -import { - useWriteContract, - useWaitForTransactionReceipt, -} from "wagmi"; -import { parseEther, encodeFunctionData } from "viem"; -import { RPCIndicator } from "./RPCIndicator"; - -import { ConnectButton } from "@rainbow-me/rainbowkit"; - -interface AddressDisplayProps { - address: Address; - addressHash: string; - chainId?: string; - transactionsResult?: AddressTransactionsResult | null; - transactionDetails?: Transaction[]; - loadingTxDetails?: boolean; - metadata?: RPCMetadata; - selectedProvider?: string | null; - onProviderSelect?: (provider: string) => void; -} - -const AddressDisplay: React.FC = React.memo( - ({ - address, - addressHash, - chainId = "1", - transactionsResult, - transactionDetails = [], - loadingTxDetails = false, - metadata, - selectedProvider, - onProviderSelect, - }) => { - const [storageSlot, setStorageSlot] = useState(""); - const [storageValue, setStorageValue] = useState(""); - const [showContractDetails, setShowContractDetails] = useState(false); - const [selectedWriteFunction, setSelectedWriteFunction] = - useState(null); - const [selectedReadFunction, setSelectedReadFunction] = useState(null); - const [functionInputs, setFunctionInputs] = useState< - Record - >({}); - const [readFunctionResult, setReadFunctionResult] = useState(null); - const [isReadingFunction, setIsReadingFunction] = useState(false); - const { jsonFiles, rpcUrls } = useContext(AppContext); - - // Wagmi hooks for contract interaction - const { - data: hash, - writeContract, - isPending, - isError, - error, - } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = - useWaitForTransactionReceipt({ hash }); - - const isContract = useMemo( - () => address.code && address.code !== "0x", - [address.code], - ); - - // Fetch Sourcify data only if it's a contract - const { - data: sourcifyData, - loading: sourcifyLoading, - isVerified, - } = useSourcify( - Number(chainId), - isContract ? addressHash : undefined, - true, - ); - - // Memoized helper functions - const truncate = useCallback((str: string, start = 6, end = 4) => { - if (!str) return ""; - if (str.length <= start + end) return str; - return `${str.slice(0, start)}...${str.slice(-end)}`; - }, []); - - const formatBalance = useCallback((balance: string) => { - try { - const eth = Number(balance) / 1e18; - return `${eth.toFixed(6)} ETH`; - } catch (e) { - return balance; - } - }, []); - - const formatValue = useCallback((value: string) => { - try { - const eth = Number(value) / 1e18; - return `${eth.toFixed(6)} ETH`; - } catch (e) { - return "0 ETH"; - } - }, []); - - // Memoized formatted balance - const formattedBalance = useMemo( - () => formatBalance(address.balance), - [address.balance, formatBalance], - ); - - const handleGetStorage = useCallback(() => { - // Check if the slot exists in the storeageAt object - if (address.storeageAt?.[storageSlot]) { - setStorageValue(address.storeageAt[storageSlot]); - } else { - setStorageValue( - "0x0000000000000000000000000000000000000000000000000000000000000000", - ); - } - }, [address.storeageAt, storageSlot]); - - // Check if we have local artifact data for this address - const localArtifact = jsonFiles[addressHash.toLowerCase()]; - - // Parse local artifact to sourcify format if it exists - memoized - const parsedLocalData = useMemo(() => { - if (!localArtifact) return null; - return { - name: localArtifact.contractName, - compilerVersion: localArtifact.buildInfo?.solcLongVersion, - evmVersion: localArtifact.buildInfo?.input?.settings?.evmVersion, - abi: localArtifact.abi, - files: localArtifact.sourceCode - ? [ - { - name: localArtifact.sourceName || "Contract.sol", - path: localArtifact.sourceName || "Contract.sol", - content: localArtifact.sourceCode, - }, - ] - : undefined, - metadata: { - language: localArtifact.buildInfo?.input?.language, - compiler: localArtifact.buildInfo - ? { - version: localArtifact.buildInfo.solcVersion, - } - : undefined, - }, - match: "perfect" as const, - creation_match: null, - runtime_match: null, - chainId: chainId, - address: addressHash, - verifiedAt: undefined, - }; - }, [localArtifact, chainId, addressHash]); - - // Use local artifact data if available and sourcify is not verified, otherwise use sourcify - const contractData = useMemo( - () => (isVerified && sourcifyData ? sourcifyData : parsedLocalData), - [isVerified, sourcifyData, parsedLocalData], - ); - - const handleWriteFunction = useCallback(async () => { - if (!selectedWriteFunction) return; - - try { - // Prepare function arguments - const args: any[] = []; - if ( - selectedWriteFunction.inputs && - selectedWriteFunction.inputs.length > 0 - ) { - for (const input of selectedWriteFunction.inputs) { - const paramName = - input.name || - `param${selectedWriteFunction.inputs.indexOf(input)}`; - const value = functionInputs[paramName]; - - if (!value && value !== "0") { - alert(`Please provide value for ${paramName}`); - return; - } - - args.push(value); - } - } - - // Prepare transaction value for payable functions - let txValue: bigint | undefined; - if ( - selectedWriteFunction.stateMutability === "payable" && - functionInputs["_value"] - ) { - try { - txValue = parseEther(functionInputs["_value"]); - } catch (e) { - alert("Invalid ETH value"); - return; - } - } - - // Call the contract - writeContract({ - address: addressHash as `0x${string}`, - abi: contractData?.abi || [], - functionName: selectedWriteFunction.name, - args: args, - value: txValue, - }); - } catch (err) { - console.error("Error writing to contract:", err); - alert(`Error: ${err instanceof Error ? err.message : "Unknown error"}`); - } - }, [ - selectedWriteFunction, - functionInputs, - addressHash, - contractData?.abi, - writeContract, - ]); - - const handleReadFunction = useCallback(async () => { - if (!selectedReadFunction || !contractData) return; - - setIsReadingFunction(true); - setReadFunctionResult(null); - - try { - // Get RPC URL for the current chain - const chainIdNum = Number(chainId); - const rpcUrlsForChain = rpcUrls[chainIdNum as keyof typeof rpcUrls]; - - if (!rpcUrlsForChain) { - throw new Error(`No RPC URL configured for chain ${chainId}`); - } - - // Get first RPC URL (could be string or array) - const rpcUrl = Array.isArray(rpcUrlsForChain) - ? rpcUrlsForChain[0] - : rpcUrlsForChain; - - if (!rpcUrl) { - // Defensive: ensure rpcUrl is defined before calling fetch - throw new Error(`No RPC URL configured for chain ${chainId}`); - } - - // Prepare function arguments - const args: any[] = []; - if ( - selectedReadFunction.inputs && - selectedReadFunction.inputs.length > 0 - ) { - for (const input of selectedReadFunction.inputs) { - const paramName = - input.name || - `param${selectedReadFunction.inputs.indexOf(input)}`; - const value = functionInputs[paramName]; - - if (!value && value !== "0") { - alert(`Please provide value for ${paramName}`); - setIsReadingFunction(false); - return; - } - - args.push(value); - } - } - - // Use fetch to call the RPC directly for read functions - const response = await fetch(rpcUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - method: "eth_call", - params: [ - { - to: addressHash, - data: encodeFunctionData({ - abi: contractData.abi, - functionName: selectedReadFunction.name, - args: args, - }), - }, - "latest", - ], - id: 1, - }), - }); - - const data = await response.json(); - - if (data.error) { - throw new Error(data.error.message || "Contract call failed"); - } - - setReadFunctionResult(data.result); - } catch (err) { - console.error("Error reading from contract:", err); - setReadFunctionResult( - `Error: ${err instanceof Error ? err.message : "Unknown error"}`, - ); - } finally { - setIsReadingFunction(false); - } - }, [ - selectedReadFunction, - contractData, - chainId, - functionInputs, - addressHash, - ]); - - return ( -
-
-
- Address - {addressHash} -
- {metadata && selectedProvider !== undefined && onProviderSelect && ( - - )} -
- -
- {/* Address Details Section */} -
-
- Address Details -
- - {/* Type */} -
- Type: - - {isContract ? ( - - šŸ“„ Contract - - ) : ( - - šŸ‘¤ Externally Owned Account (EOA) - - )} - -
- - {/* Balance */} -
- Balance: - - - {formatBalance(address.balance)} - - -
- - {/* Transaction Count (Nonce) */} -
- Transactions: - - {Number(address.txCount).toLocaleString()} txns - -
- - {/* Verification Status (only for contracts) */} - {isContract && ( - <> -
- Contract Verified: - - {sourcifyLoading ? ( - - Checking Sourcify... - - ) : isVerified || parsedLocalData ? ( - - āœ“ Verified - {contractData?.match && ( - - {contractData.match === "perfect" - ? parsedLocalData - ? "Local JSON" - : "Perfect Match" - : "Partial Match"} - - )} - - ) : ( - - Not Verified - - )} - -
- - )} - - {/* Contract Name (if verified) */} - {isContract && contractData?.name && ( -
- Contract Name: - {contractData.name} -
- )} - - {/* Compiler Version (if verified) */} - {isContract && contractData?.compilerVersion && ( -
- Compiler: - - {contractData.compilerVersion} - -
- )} -
- - {/* Contract Verification Details */} - {isContract && (isVerified || parsedLocalData) && contractData && ( -
-
setShowContractDetails(!showContractDetails)} - > - Contract Details - - {showContractDetails ? " ā–¼" : " ā–¶"} - -
- - {showContractDetails && ( - <> - {contractData.name && ( -
- Contract Name - - {contractData.name} - -
- )} - - {contractData.compilerVersion && ( -
- Compiler Version - - {contractData.compilerVersion} - -
- )} - - {contractData.evmVersion && ( -
- EVM Version - - {contractData.evmVersion} - -
- )} - - {contractData.chainId && ( -
- Chain ID - {contractData.chainId} -
- )} - - {contractData.verifiedAt && ( -
- Verified At - - {new Date(contractData.verifiedAt).toLocaleString()} - -
- )} - - {contractData.match && ( -
- Match Type - - {contractData.match.toUpperCase()} - -
- )} - - {contractData.metadata?.compiler && ( -
- Compiler - - {contractData.metadata.compiler.version} - -
- )} - - {contractData.creation_match && ( -
- Creation Match - - {contractData.creation_match.toUpperCase()} - -
- )} - - {contractData.runtime_match && ( -
- Runtime Match - - {contractData.runtime_match.toUpperCase()} - -
- )} - - {/* Contract Bytecode */} -
-
{ - const elem = - document.getElementById("bytecode-content"); - const icon = document.getElementById("bytecode-icon"); - if (elem && icon) { - const isHidden = elem.style.display === "none"; - elem.style.display = isHidden ? "block" : "none"; - icon.textContent = isHidden ? "ā–¼" : "ā–¶"; - } - }} - > - Contract Bytecode - - ā–¶ - -
-
- {address.code} -
-
- - {/* Source Code */} - {((contractData.files && contractData.files.length > 0) || - (contractData as any).sources) && - (() => { - // Prepare source files array - either from files or sources object - const sources = (contractData as any).sources; - const sourceFiles = - contractData.files && contractData.files.length > 0 - ? contractData.files - : sources - ? Object.entries(sources).map( - ([path, source]: [string, any]) => ({ - name: path, - path: path, - content: source.content || "", - }), - ) - : []; - - return sourceFiles.length > 0 ? ( -
-
{ - const elem = document.getElementById( - "source-code-content", - ); - const icon = - document.getElementById("source-code-icon"); - if (elem && icon) { - const isHidden = elem.style.display === "none"; - elem.style.display = isHidden - ? "block" - : "none"; - icon.textContent = isHidden ? "ā–¼" : "ā–¶"; - } - }} - > - Source Code - - ā–¶ - -
-
- {sourceFiles.map((file: any, idx: number) => ( -
-
- šŸ“„ {file.name || file.path} -
-
-																	{file.content}
-																
-
- ))} -
-
- ) : null; - })()} - - {/* Raw ABI */} - {contractData.abi && contractData.abi.length > 0 && ( -
-
{ - const elem = - document.getElementById("raw-abi-content"); - const icon = document.getElementById("raw-abi-icon"); - if (elem && icon) { - const isHidden = elem.style.display === "none"; - elem.style.display = isHidden ? "block" : "none"; - icon.textContent = isHidden ? "ā–¼" : "ā–¶"; - } - }} - > - Raw ABI - - ā–¶ - -
-
- {JSON.stringify(contractData.abi, null, 2)} -
-
- )} - - {/* Contract ABI */} - {contractData.abi && contractData.abi.length > 0 && ( -
-
- Functions - - {({ - account, - chain, - openAccountModal, - openChainModal, - openConnectModal, - authenticationStatus, - mounted, - }: any) => { - const ready = - mounted && authenticationStatus !== "loading"; - const connected = - ready && - account && - chain && - (!authenticationStatus || - authenticationStatus === "authenticated"); - - return ( -
- {(() => { - if (!connected) { - return ( - - ); - } - - if (chain.unsupported) { - return ( - - ); - } - - return ( -
- - -
- ); - })()} -
- ); - }} -
-
-
- {/* Read Functions (view/pure) */} - {(() => { - const readFunctions = contractData.abi.filter( - (item: any) => - item.type === "function" && - (item.stateMutability === "view" || - item.stateMutability === "pure"), - ); - return ( - readFunctions.length > 0 && ( -
-
- Read Functions ({readFunctions.length}) -
-
- {readFunctions.map( - (func: any, idx: number) => ( - - ), - )} -
-
- ) - ); - })()} - - {/* Write Functions (payable/nonpayable) */} - {(() => { - const writeFunctions = contractData.abi.filter( - (item: any) => - item.type === "function" && - (item.stateMutability === "payable" || - item.stateMutability === "nonpayable" || - !item.stateMutability), - ); - return ( - writeFunctions.length > 0 && ( -
-
- Write Functions ({writeFunctions.length}) -
-
- {writeFunctions.map( - (func: any, idx: number) => ( - - ), - )} -
-
- ) - ); - })()} - - {/* Events */} - {contractData.abi.filter( - (item: any) => item.type === "event", - ).length > 0 && ( -
-
- Events ( - { - contractData.abi.filter( - (item: any) => item.type === "event", - ).length - } - ) -
-
- {contractData.abi - .filter((item: any) => item.type === "event") - .slice(0, 10) - .map((event: any, idx: number) => ( - - {event.name} - - ))} - {contractData.abi.filter( - (item: any) => item.type === "event", - ).length > 10 && ( - - + - {contractData.abi.filter( - (item: any) => item.type === "event", - ).length - 10}{" "} - more - - )} -
-
- )} - - {/* Read Function Form */} - {selectedReadFunction && ( -
-
- {selectedReadFunction.name} -
- - {selectedReadFunction.inputs && - selectedReadFunction.inputs.length > 0 ? ( -
- {selectedReadFunction.inputs.map( - (input: any, idx: number) => ( -
- - - setFunctionInputs({ - ...functionInputs, - [input.name || `param${idx}`]: - e.target.value, - }) - } - placeholder={`Enter ${input.type}`} - style={{ - width: "100%", - padding: "8px 12px", - background: "rgba(0, 0, 0, 0.3)", - border: - "1px solid rgba(59, 130, 246, 0.3)", - borderRadius: "6px", - color: "#fff", - fontSize: "0.85rem", - fontFamily: "monospace", - }} - /> -
- ), - )} -
- ) : ( -
- No parameters required -
- )} - - {/* Read Result */} - {readFunctionResult !== null && ( -
-
- {readFunctionResult?.startsWith("Error") - ? "āŒ Error" - : "āœ… Result"} -
- {readFunctionResult} -
- )} - -
- - -
-
- )} - - {/* Write Function Form */} - {selectedWriteFunction && ( -
-
- {selectedWriteFunction.name} - {selectedWriteFunction.stateMutability === - "payable" && ( - - payable - - )} -
- - {selectedWriteFunction.inputs && - selectedWriteFunction.inputs.length > 0 ? ( -
- {selectedWriteFunction.inputs.map( - (input: any, idx: number) => ( -
- - - setFunctionInputs({ - ...functionInputs, - [input.name || `param${idx}`]: - e.target.value, - }) - } - placeholder={`Enter ${input.type}`} - style={{ - width: "100%", - padding: "8px 12px", - background: "rgba(0, 0, 0, 0.3)", - border: - "1px solid rgba(245, 158, 11, 0.3)", - borderRadius: "6px", - color: "#fff", - fontSize: "0.85rem", - fontFamily: "monospace", - }} - /> -
- ), - )} -
- ) : ( -
- No parameters required -
- )} - - {selectedWriteFunction.stateMutability === - "payable" && ( -
- - - setFunctionInputs({ - ...functionInputs, - _value: e.target.value, - }) - } - placeholder="0.0" - style={{ - width: "100%", - padding: "8px 12px", - background: "rgba(0, 0, 0, 0.3)", - border: "1px solid rgba(16, 185, 129, 0.3)", - borderRadius: "6px", - color: "#fff", - fontSize: "0.85rem", - fontFamily: "monospace", - }} - /> -
- )} - - {/* Transaction Status */} - {(isPending || - isConfirming || - isConfirmed || - isError) && ( -
- {isPending && - "ā³ Waiting for wallet confirmation..."} - {isConfirming && - "ā³ Waiting for transaction confirmation..."} - {isConfirmed && ( -
- āœ… Transaction confirmed! - {hash && ( -
- - View transaction - -
- )} -
- )} - {isError && ( -
- āŒ Error:{" "} - {error?.message || "Transaction failed"} -
- )} -
- )} - -
- - -
-
- )} -
-
- )} - - {sourcifyData && ( - - )} - - )} -
- )} - - {/* Last Transactions Section */} -
-
- Last Transactions - {transactionsResult && ( - - {transactionsResult.source === "trace_filter" && ( - <> - ā— - Complete history ({transactionDetails.length}{" "} - transactions) - - )} - {transactionsResult.source === "logs" && ( - <> - ā— - Partial (logs only) - {transactionDetails.length}{" "} - transactions - - )} - {transactionsResult.source === "none" && ( - <> - ā— - No data available - - )} - - )} -
- - {/* Warning message for partial data */} - {transactionsResult?.message && ( -
- - {transactionsResult.source === "none" ? "āš ļø" : "ā„¹ļø"} - - {transactionsResult.message} -
- )} - - {/* Loading state */} - {loadingTxDetails && ( -
- Loading transaction details... -
- )} - - {/* Transaction table */} - {!loadingTxDetails && transactionDetails.length > 0 && ( -
- - - - - - - - - - - - {transactionDetails.map((tx) => ( - - - - - - - - ))} - -
TX HashFromToValueStatus
- - {truncate(tx.hash, 8, 6)} - - - - {tx.from?.toLowerCase() === - addressHash.toLowerCase() - ? "This Address" - : truncate(tx.from || "", 6, 4)} - - - {tx.to ? ( - - {tx.to?.toLowerCase() === - addressHash.toLowerCase() - ? "This Address" - : truncate(tx.to, 6, 4)} - - ) : ( - - Contract Creation - - )} - - - {formatValue(tx.value)} - - - {tx.receipt?.status === "0x1" ? ( - - āœ“ Success - - ) : tx.receipt?.status === "0x0" ? ( - - āœ— Failed - - ) : ( - - ā³ Pending - - )} -
-
- )} - - {/* Empty state */} - {!loadingTxDetails && - transactionDetails.length === 0 && - !transactionsResult?.message && ( -
- No transactions found for this address -
- )} -
- - {/* Storage Section (for contracts) */} - {isContract && ( -
-
- Contract Storage -
-
-
- Storage Slot: - -
- setStorageSlot(e.target.value)} - className="storage-input" - /> - -
-
-
- {storageValue && ( -
- Value: - -
- {storageValue} -
-
-
- )} -
-
- )} -
-
- ); - }, -); - -AddressDisplay.displayName = "AddressDisplay"; - -export default AddressDisplay; diff --git a/src/components/common/BlockDisplay.tsx b/src/components/common/BlockDisplay.tsx deleted file mode 100644 index f7a765a..0000000 --- a/src/components/common/BlockDisplay.tsx +++ /dev/null @@ -1,492 +0,0 @@ -import React, { useState } from "react"; -import { Link } from "react-router-dom"; -import type { Block, BlockArbitrum, RPCMetadata } from "../../types"; -import { RPCIndicator } from "./RPCIndicator"; - -interface BlockDisplayProps { - block: Block | BlockArbitrum; - chainId?: string; - metadata?: RPCMetadata; - selectedProvider?: string | null; - onProviderSelect?: (provider: string) => void; -} - -const BlockDisplay: React.FC = React.memo( - ({ block, chainId, metadata, selectedProvider, onProviderSelect }) => { - const [showWithdrawals, setShowWithdrawals] = useState(false); - const [showTransactions, setShowTransactions] = useState(false); - const [showMoreDetails, setShowMoreDetails] = useState(false); - - // Check if this is an Arbitrum block - const isArbitrumBlock = ( - block: Block | BlockArbitrum, - ): block is BlockArbitrum => { - return "l1BlockNumber" in block; - }; - - // Helper to format timestamp - const formatTimestamp = (timestamp: string) => { - try { - const ts = Number(timestamp); - const date = new Date(ts * 1000); - return new Intl.DateTimeFormat(undefined, { - year: "numeric", - month: "short", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - timeZoneName: "short", - }).format(date); - } catch (e) { - return timestamp; - } - }; - - const formatTimeAgo = (timestamp: string) => { - const ts = Number(timestamp) * 1000; - const diffMs = Date.now() - ts; - const diffSeconds = Math.floor(Math.abs(diffMs) / 1000); - - if (diffSeconds < 5) { - return diffMs >= 0 ? "just now" : "in a few seconds"; - } - - const units = [ - { label: "day", seconds: 60 * 60 * 24 }, - { label: "hour", seconds: 60 * 60 }, - { label: "minute", seconds: 60 }, - ]; - - for (const unit of units) { - if (diffSeconds >= unit.seconds) { - const value = Math.floor(diffSeconds / unit.seconds); - const plural = value === 1 ? "" : "s"; - return diffMs >= 0 - ? `${value} ${unit.label}${plural} ago` - : `in ${value} ${unit.label}${plural}`; - } - } - - return diffMs >= 0 - ? `${diffSeconds} second${diffSeconds === 1 ? "" : "s"} ago` - : `in ${diffSeconds} second${diffSeconds === 1 ? "" : "s"}`; - }; - - const formatGwei = (value: string) => { - try { - const gwei = Number(value) / 1e9; - return `${gwei.toFixed(9)} Gwei`; - } catch (e) { - return value; - } - }; - - const formatEth = (value: string) => { - try { - const eth = Number(value) / 1e18; - return `${eth.toFixed(12)} ETH`; - } catch (e) { - return value; - } - }; - - const blockNumber = Number(block.number); - const timestampFormatted = formatTimestamp(block.timestamp); - const timestampAge = formatTimeAgo(block.timestamp); - const gasUsedPct = ( - (Number(block.gasUsed) / Number(block.gasLimit)) * - 100 - ).toFixed(1); - - // Calculate burnt fees if baseFeePerGas exists - const burntFees = block.baseFeePerGas - ? (BigInt(block.gasUsed) * BigInt(block.baseFeePerGas)).toString() - : null; - - return ( -
-
- Block Details - {metadata && selectedProvider !== undefined && onProviderSelect && ( - - )} -
- -
- {/* Block Height */} -
- Block Height: - - - {blockNumber.toLocaleString()} - - {chainId && ( - - {blockNumber > 0 && ( - - ← - - )} - - → - - - )} - -
- - {/* Timestamp */} -
- Timestamp: - - {timestampAge} - ({timestampFormatted}) - -
- - {/* Transactions */} -
- Transactions: - - - {block.transactions ? block.transactions.length : 0}{" "} - transactions - {" "} - in this block - -
- - {/* Withdrawals count */} - {block.withdrawals && block.withdrawals.length > 0 && ( -
- Withdrawals: - - {block.withdrawals.length} withdrawal - {block.withdrawals.length !== 1 ? "s" : ""} in this block - -
- )} - - {/* Fee Recipient (Miner) */} -
- Fee Recipient: - - {chainId ? ( - - {block.miner} - - ) : ( - block.miner - )} - -
- - {/* Gas Used */} -
- Gas Used: - - {Number(block.gasUsed).toLocaleString()} - ({gasUsedPct}%) - -
- - {/* Gas Limit */} -
- Gas Limit: - - {Number(block.gasLimit).toLocaleString()} - -
- - {/* Base Fee Per Gas */} - {block.baseFeePerGas && ( -
- Base Fee Per Gas: - - {formatGwei(block.baseFeePerGas)} - -
- )} - - {/* Burnt Fees */} - {burntFees && ( -
- Burnt Fees: - - šŸ”„ {formatEth(burntFees)} - -
- )} - - {/* Extra Data */} - {block.extraData && block.extraData !== "0x" && ( -
- Extra Data: - {block.extraData} -
- )} - - {/* Difficulty */} - {Number(block.difficulty) > 0 && ( -
- Difficulty: - - {Number(block.difficulty).toLocaleString()} - -
- )} - - {/* Total Difficulty */} - {Number(block.totalDifficulty) > 0 && ( -
- Total Difficulty: - - {Number(block.totalDifficulty).toLocaleString()} - -
- )} - - {/* Size */} -
- Size: - - {Number(block.size).toLocaleString()} bytes - -
- - {/* Arbitrum-specific fields */} - {isArbitrumBlock(block) && ( - <> -
- L1 Block Number: - - {Number(block.l1BlockNumber).toLocaleString()} - -
-
- Send Count: - {block.sendCount} -
-
- Send Root: - {block.sendRoot} -
- - )} - - {/* More Details (collapsible) */} -
- - - {showMoreDetails && ( -
-
- Hash: - {block.hash} -
-
- Parent Hash: - - {chainId && - block.parentHash !== - "0x0000000000000000000000000000000000000000000000000000000000000000" ? ( - - {block.parentHash} - - ) : ( - block.parentHash - )} - -
-
- State Root: - - {block.stateRoot} - -
-
- Transactions Root: - - {block.transactionsRoot} - -
-
- Receipts Root: - - {block.receiptsRoot} - -
- {block.withdrawalsRoot && ( -
- Withdrawals Root: - - {block.withdrawalsRoot} - -
- )} -
- Logs Bloom: -
- {block.logsBloom} -
-
-
- Nonce: - {block.nonce} -
-
- Mix Hash: - {block.mixHash} -
-
- Sha3 Uncles: - - {block.sha3Uncles} - -
-
- )} -
-
- - {/* Transactions List */} - {block.transactions && block.transactions.length > 0 && ( -
-
- -
- {showTransactions && ( -
- {block.transactions.map((txHash, index) => ( -
- {index} - - {chainId ? ( - - {txHash} - - ) : ( - txHash - )} - -
- ))} -
- )} -
- )} - - {/* Withdrawals List */} - {block.withdrawals && block.withdrawals.length > 0 && ( -
-
- -
- {showWithdrawals && ( -
- {block.withdrawals.map((withdrawal, index) => ( -
-
{index}
-
-
- Index - - {Number(withdrawal.index).toLocaleString()} - -
-
- Validator - - {Number(withdrawal.validatorIndex).toLocaleString()} - -
-
- Address - - {chainId ? ( - - {withdrawal.address} - - ) : ( - withdrawal.address - )} - -
-
- Amount - - {(Number(withdrawal.amount) / 1e9).toFixed(9)} ETH - -
-
-
- ))} -
- )} -
- )} -
- ); - }, -); - -BlockDisplay.displayName = "BlockDisplay"; - -export default BlockDisplay; diff --git a/src/components/common/ErrorBoundary.tsx b/src/components/common/ErrorBoundary.tsx index 16b93a8..0514729 100644 --- a/src/components/common/ErrorBoundary.tsx +++ b/src/components/common/ErrorBoundary.tsx @@ -1,125 +1,129 @@ -import React, { Component, ErrorInfo, ReactNode } from "react"; +import React, { Component, type ErrorInfo, type ReactNode } from "react"; interface Props { - children: ReactNode; - fallback?: ReactNode; + children: ReactNode; + fallback?: ReactNode; } interface State { - hasError: boolean; - error?: Error; - errorInfo?: ErrorInfo; + hasError: boolean; + error?: Error; + errorInfo?: ErrorInfo; } export class ErrorBoundary extends Component { - constructor(props: Props) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(error: Error): State { - // Update state so the next render will show the fallback UI - return { hasError: true, error }; - } - - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - // Log error details - console.error("ErrorBoundary caught an error:", error, errorInfo); - - this.setState({ - error, - errorInfo, - }); - - // You can also log the error to an error reporting service here - // Example: errorReportingService.captureException(error, { extra: errorInfo }); - } - - private handleReload = () => { - window.location.reload(); - }; - - private handleGoHome = () => { - window.location.href = "/"; - }; - - render() { - if (this.state.hasError) { - // Custom fallback UI - if (this.props.fallback) { - return this.props.fallback; - } - - // Default error UI - return ( -
-
-
-

āš ļø Something went wrong

-

- We're sorry, but something unexpected happened. The application - encountered an error. -

- -
- - -
- - {process.env.NODE_ENV === "development" && this.state.error && ( -
- Error Details (Development Mode) -
-

Error:

-
{this.state.error.toString()}
- - {this.state.errorInfo && ( - <> -

Component Stack:

-
{this.state.errorInfo.componentStack}
- - )} -
-
- )} -
-
-
- ); - } - - return this.props.children; - } + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + // Update state so the next render will show the fallback UI + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + // Log error details + console.error("ErrorBoundary caught an error:", error, errorInfo); + + this.setState({ + error, + errorInfo, + }); + + // You can also log the error to an error reporting service here + // Example: errorReportingService.captureException(error, { extra: errorInfo }); + } + + private handleReload = () => { + window.location.reload(); + }; + + private handleGoHome = () => { + const isGhPages = + typeof window !== "undefined" && window.location.hostname.includes("github.io"); + window.location.href = isGhPages ? "/explorer/" : "/"; + }; + + render() { + if (this.state.hasError) { + // Custom fallback UI + if (this.props.fallback) { + return this.props.fallback; + } + + // Default error UI + return ( +
+
+
+

āš ļø Something went wrong

+

+ We're sorry, but something unexpected happened. The application encountered an + error. +

+ +
+ {/** biome-ignore lint/a11y/useButtonType: */} + + {/** biome-ignore lint/a11y/useButtonType: */} + +
+ + {process.env.NODE_ENV === "development" && this.state.error && ( +
+ Error Details (Development Mode) +
+

Error:

+
{this.state.error.toString()}
+ + {this.state.errorInfo && ( + <> +

Component Stack:

+
{this.state.errorInfo.componentStack}
+ + )} +
+
+ )} +
+
+
+ ); + } + + return this.props.children; + } } // Hook version for functional components that need error boundary functionality export const useErrorHandler = () => { - const [error, setError] = React.useState(null); - - const resetError = React.useCallback(() => { - setError(null); - }, []); - - const captureError = React.useCallback((error: Error) => { - console.error("Error captured:", error); - setError(error); - }, []); - - React.useEffect(() => { - if (error) { - throw error; - } - }, [error]); - - return { - captureError, - resetError, - hasError: !!error, - }; + const [error, setError] = React.useState(null); + + const resetError = React.useCallback(() => { + setError(null); + }, []); + + const captureError = React.useCallback((error: Error) => { + console.error("Error captured:", error); + setError(error); + }, []); + + React.useEffect(() => { + if (error) { + throw error; + } + }, [error]); + + return { + captureError, + resetError, + hasError: !!error, + }; }; export default ErrorBoundary; diff --git a/src/components/common/ExtraDataDisplay.tsx b/src/components/common/ExtraDataDisplay.tsx new file mode 100644 index 0000000..2316ee0 --- /dev/null +++ b/src/components/common/ExtraDataDisplay.tsx @@ -0,0 +1,74 @@ +import type React from "react"; +import { useMemo, useState } from "react"; + +interface ExtraDataDisplayProps { + hexData: string; + showToggle?: boolean; +} + +const ExtraDataDisplay: React.FC = ({ hexData, showToggle = true }) => { + const [showRaw, setShowRaw] = useState(false); + + const decoded = useMemo(() => { + if (!hexData || hexData === "0x") return null; + + try { + // Remove 0x prefix + const hex = hexData.startsWith("0x") ? hexData.slice(2) : hexData; + + // Convert hex to bytes + const bytes: number[] = []; + for (let i = 0; i < hex.length; i += 2) { + bytes.push(Number.parseInt(hex.substr(i, 2), 16)); + } + + // Check if all bytes are printable ASCII (32-126) + const isPrintable = bytes.every((b) => b >= 32 && b <= 126); + + if (isPrintable && bytes.length > 0) { + return String.fromCharCode(...bytes); + } + + // Try UTF-8 decoding + const decoder = new TextDecoder("utf-8", { fatal: true }); + const uint8Array = new Uint8Array(bytes); + const decoded = decoder.decode(uint8Array); + + // Check if result contains mostly printable characters + const printableCount = decoded.split("").filter((c) => c.charCodeAt(0) >= 32).length; + if (printableCount / decoded.length > 0.8) { + return decoded; + } + + return null; + } catch { + return null; + } + }, [hexData]); + + const hasDecodedValue = decoded !== null && decoded.length > 0; + const displayValue = showRaw || !hasDecodedValue ? hexData : decoded; + + return ( +
+ + {hasDecodedValue && !showRaw && ( + "{displayValue}" + )} + {(!hasDecodedValue || showRaw) && {displayValue}} + + {showToggle && hasDecodedValue && ( + + )} +
+ ); +}; + +export default ExtraDataDisplay; diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx index 128bc32..cac2c08 100644 --- a/src/components/common/Footer.tsx +++ b/src/components/common/Footer.tsx @@ -1,73 +1,105 @@ -import React from "react"; -import { Link } from "react-router-dom"; +import type React from "react"; +import { useNavigate } from "react-router"; import { ENVIRONMENT } from "../../utils/constants"; interface FooterProps { - className?: string; + className?: string; } const Footer: React.FC = ({ className = "" }) => { - // Get commit hash from environment variable, fallback to 'development' - const commitHash = process.env.REACT_APP_COMMIT_HASH || "development"; + const navigate = useNavigate(); - // Format commit hash - show first 7 characters if it's a full hash - const formattedCommitHash = - commitHash.length > 7 ? commitHash.substring(0, 7) : commitHash; + // Get commit hash from environment variable, fallback to 'development' + const commitHash = process.env.REACT_APP_COMMIT_HASH || "development"; - // Get version from environment variable or fallback - const appVersion = process.env.REACT_APP_VERSION || "0.1.0"; + // Format commit hash - show first 7 characters if it's a full hash + const formattedCommitHash = commitHash.length > 7 ? commitHash.substring(0, 7) : commitHash; - // Get the GitHub repository URL from package.json or environment - const repoUrl = - process.env.REACT_APP_GITHUB_REPO || "https://github.com/openscan-explorer/explorer"; + // Get version from environment variable or fallback + const appVersion = process.env.REACT_APP_VERSION || "0.1.0"; - // Determine footer version class based on environment - const getVersionClass = () => { - if (ENVIRONMENT === "staging") return "footer-version-staging"; - if (ENVIRONMENT === "development") return "footer-version-development"; - return ""; - }; - return ( - - ); + // Get the GitHub repository URL from package.json or environment + const repoUrl = + process.env.REACT_APP_GITHUB_REPO || "https://github.com/openscan-explorer/explorer"; + + // Determine footer version class based on environment + const getVersionClass = () => { + if (ENVIRONMENT === "staging") return "footer-version-staging"; + if (ENVIRONMENT === "development") return "footer-version-development"; + return ""; + }; + + const goToSubscriptions = () => { + navigate("/subscriptions"); + }; + + const goToSupporters = () => { + navigate("/supporters"); + }; + return ( + + ); }; export default Footer; diff --git a/src/components/common/IsometricBlocks.tsx b/src/components/common/IsometricBlocks.tsx index fe97bcb..90f8c0a 100644 --- a/src/components/common/IsometricBlocks.tsx +++ b/src/components/common/IsometricBlocks.tsx @@ -1,162 +1,139 @@ -import React, { - useState, - useEffect, - useCallback, - useMemo, - useRef, -} from "react"; +import type React from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; // Network colors matching the logo - Ethereum weighted more heavily const NETWORKS = [ - { color: "#627EEA", weight: 70 }, // Ethereum - most common - { color: "#FFF100", weight: 6 }, // Hardhat/Local - { color: "#28A0F0", weight: 6 }, // Arbitrum - { color: "#FF0420", weight: 6 }, // Optimism - { color: "#0052FF", weight: 6 }, // Base - { color: "#CFB4FF", weight: 6 }, // Sepolia + { color: "#627EEA", weight: 70 }, // Ethereum - most common + { color: "#FFF100", weight: 6 }, // Hardhat/Local + { color: "#28A0F0", weight: 6 }, // Arbitrum + { color: "#FF0420", weight: 6 }, // Optimism + { color: "#0052FF", weight: 6 }, // Base + { color: "#CFB4FF", weight: 6 }, // Sepolia ]; // Create weighted array for random selection const WEIGHTED_COLORS = NETWORKS.flatMap((n) => Array(n.weight).fill(n.color)); interface CubeData { - id: number; - x: number; - y: number; - color: string; + id: number; + x: number; + y: number; + color: string; } interface IsometricCubeProps { - x: number; - y: number; - size: number; - color: string; + x: number; + y: number; + size: number; + color: string; } const IsometricCube: React.FC = ({ x, y, size, color }) => { - // Isometric cube dimensions - const h = size * 0.5; // height offset for 3D effect - - // Top face (brightest) - const topPoints = `${x},${y - h} ${x + size},${y} ${x},${y + h} ${x - size},${y}`; - - // Left face (medium) - const leftPoints = `${x - size},${y} ${x},${y + h} ${x},${y + h + size} ${x - size},${y + size}`; - - // Right face (darkest) - const rightPoints = `${x},${y + h} ${x + size},${y} ${x + size},${y + size} ${x},${y + h + size}`; - - return ( - - {/* Right face - darkest */} - - {/* Left face - medium */} - - {/* Top face - brightest */} - - - ); + // Isometric cube dimensions + const h = size * 0.5; // height offset for 3D effect + + // Top face (brightest) + const topPoints = `${x},${y - h} ${x + size},${y} ${x},${y + h} ${x - size},${y}`; + + // Left face (medium) + const leftPoints = `${x - size},${y} ${x},${y + h} ${x},${y + h + size} ${x - size},${y + size}`; + + // Right face (darkest) + const rightPoints = `${x},${y + h} ${x + size},${y} ${x + size},${y + size} ${x},${y + h + size}`; + + return ( + + {/* Right face - darkest */} + + {/* Left face - medium */} + + {/* Top face - brightest */} + + + ); }; interface IsometricBlocksProps { - width: number; - height: number; - cubeSize?: number; - maxCubes?: number; - spawnInterval?: number; + width: number; + height: number; + cubeSize?: number; + maxCubes?: number; + spawnInterval?: number; } export const IsometricBlocks: React.FC = ({ - width, - height, - cubeSize = 24, - maxCubes = 50, - spawnInterval = 400, + width, + height, + cubeSize = 24, + maxCubes = 50, + spawnInterval = 400, }) => { - const [cubes, setCubes] = useState([]); - const nextIdRef = useRef(0); - - // Calculate grid positions for isometric layout - const gridPositions = useMemo(() => { - const positions: { x: number; y: number }[] = []; - const stepX = cubeSize * 2; - const stepY = cubeSize * 1.5; - - for (let row = -2; row < height / stepY + 2; row++) { - const offsetX = (row % 2) * cubeSize; - for (let col = -2; col < width / stepX + 2; col++) { - positions.push({ - x: col * stepX + offsetX + cubeSize, - y: row * stepY + cubeSize, - }); - } - } - return positions; - }, [width, height, cubeSize]); - - // Spawn new cubes periodically - const spawnCube = useCallback(() => { - const pos = gridPositions[Math.floor(Math.random() * gridPositions.length)]; - if (!pos) return; - const network = - WEIGHTED_COLORS[Math.floor(Math.random() * WEIGHTED_COLORS.length)]; - if (!network) return; - - const cubeId = nextIdRef.current++; - const newCube: CubeData = { - id: cubeId, - x: pos.x, - y: pos.y, - color: network, - }; - - setCubes((prev) => { - const updated = [...prev, newCube]; - // Remove oldest cubes if we exceed max - if (updated.length > maxCubes) { - return updated.slice(-maxCubes); - } - return updated; - }); - }, [gridPositions, maxCubes]); - - // Spawn cubes periodically - useEffect(() => { - const spawn = setInterval(spawnCube, spawnInterval); - return () => clearInterval(spawn); - }, [spawnCube, spawnInterval]); - - return ( - - + {cubes.map((cube) => ( + + ))} + + ); }; export default IsometricBlocks; diff --git a/src/components/common/Loader.tsx b/src/components/common/Loader.tsx index a2022ad..f7a315f 100644 --- a/src/components/common/Loader.tsx +++ b/src/components/common/Loader.tsx @@ -1,29 +1,27 @@ import React from "react"; interface LoaderProps { - size?: number; - color?: string; - text?: string; + size?: number; + color?: string; + text?: string; } -const Loader: React.FC = React.memo( - ({ size = 40, color = "#10b981", text }) => { - return ( -
-
- {text &&

{text}

} -
- ); - }, -); +const Loader: React.FC = React.memo(({ size = 40, color = "#10b981", text }) => { + return ( +
+
+ {text &&

{text}

} +
+ ); +}); Loader.displayName = "Loader"; diff --git a/src/components/common/Loading.tsx b/src/components/common/Loading.tsx index ddee629..703a51a 100644 --- a/src/components/common/Loading.tsx +++ b/src/components/common/Loading.tsx @@ -2,13 +2,13 @@ import React from "react"; import "../../styles/styles.css"; const Loading = React.memo(() => ( -
-
-
-
-
-
-
+
+
+
+
+
+
+
)); Loading.displayName = "Loading"; diff --git a/src/components/common/LongString.tsx b/src/components/common/LongString.tsx index 4b6eafc..c59939a 100644 --- a/src/components/common/LongString.tsx +++ b/src/components/common/LongString.tsx @@ -1,43 +1,40 @@ -import React, { useState } from "react"; +import type React from "react"; +import { useState } from "react"; interface LongStringProps { - value: string; - start?: number; - end?: number; - style?: React.CSSProperties; + value: string; + start?: number; + end?: number; + style?: React.CSSProperties; } -const LongString: React.FC = ({ - value, - start = 10, - end = 8, - style = {}, -}) => { - const [isHovered, setIsHovered] = useState(false); +const LongString: React.FC = ({ value, start = 10, end = 8, style = {} }) => { + const [isHovered, setIsHovered] = useState(false); - const truncate = (str: string) => { - if (!str) return ""; - if (str.length <= start + end) return str; - return `${str.slice(0, start)}...${str.slice(-end)}`; - }; + const truncate = (str: string) => { + if (!str) return ""; + if (str.length <= start + end) return str; + return `${str.slice(0, start)}...${str.slice(-end)}`; + }; - const shouldTruncate = value && value.length > start + end; + const shouldTruncate = value && value.length > start + end; - return ( - setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - style={{ - wordBreak: isHovered && shouldTruncate ? "break-all" : "normal", - transition: "all 0.2s ease", - cursor: shouldTruncate ? "pointer" : "default", - ...style, - }} - title={shouldTruncate ? value : undefined} - > - {isHovered && shouldTruncate ? value : truncate(value)} - - ); + return ( + // biome-ignore lint/a11y/noStaticElementInteractions: + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + style={{ + wordBreak: isHovered && shouldTruncate ? "break-all" : "normal", + transition: "all 0.2s ease", + cursor: shouldTruncate ? "pointer" : "default", + ...style, + }} + title={shouldTruncate ? value : undefined} + > + {isHovered && shouldTruncate ? value : truncate(value)} + + ); }; export default LongString; diff --git a/src/components/common/Navbar.tsx b/src/components/common/Navbar.tsx deleted file mode 100644 index 80ad316..0000000 --- a/src/components/common/Navbar.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import { Link, useNavigate, useLocation } from "react-router-dom"; -import { ConnectButton } from "@rainbow-me/rainbowkit"; -import { useAccount } from "wagmi"; -import { useState } from "react"; -import { NetworkBlockIndicator } from "./NetworkBlockIndicator"; -import VersionWarningIcon from "./VersionWarningIcon"; - -const Navbar = () => { - const { address } = useAccount(); - const navigate = useNavigate(); - const location = useLocation(); - const [searchInput, setSearchInput] = useState(""); - - // Extract chainId from the pathname (e.g., /1/blocks -> 1) - const pathSegments = location.pathname.split("/").filter(Boolean); - const chainId = - pathSegments[0] && !isNaN(Number(pathSegments[0])) - ? pathSegments[0] - : undefined; - - // Check if we should show the search box (on blocks, block, txs, tx pages) - const shouldShowSearch = - chainId && - pathSegments.length >= 2 && - pathSegments[1] && - ["blocks", "block", "txs", "tx", "address"].includes(pathSegments[1]); - - console.log( - "Navbar chainId from URL:", - chainId, - "pathname:", - location.pathname, - ); - - const handleSearch = (e: React.FormEvent) => { - e.preventDefault(); - if (!searchInput.trim() || !chainId) return; - - const input = searchInput.trim(); - - // Check if it's a transaction hash (0x followed by 64 hex chars) - if (/^0x[a-fA-F0-9]{64}$/.test(input)) { - navigate(`/${chainId}/tx/${input}`); - } - // Check if it's an address (0x followed by 40 hex chars) - else if (/^0x[a-fA-F0-9]{40}$/.test(input)) { - navigate(`/${chainId}/address/${input}`); - } - // Check if it's a block number - else if (/^\d+$/.test(input)) { - navigate(`/${chainId}/block/${input}`); - } - // Check if it's a block hash (0x followed by 64 hex chars - same as tx) - else if (/^0x[a-fA-F0-9]{64}$/.test(input)) { - navigate(`/${chainId}/block/${input}`); - } - - setSearchInput(""); - }; - - const goToSettings = () => { - navigate("/settings"); - }; - - return ( - - ); -}; - -export default Navbar; diff --git a/src/components/common/NetworkBlockIndicator.tsx b/src/components/common/NetworkBlockIndicator.tsx deleted file mode 100644 index f8e1713..0000000 --- a/src/components/common/NetworkBlockIndicator.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { useEffect, useState, useContext, useMemo } from "react"; -import { useLocation } from "react-router-dom"; -import { AppContext } from "../../context/AppContext"; -import { RPCClient } from "../../services/EVM/common/RPCClient"; -import { getRPCUrls } from "../../config/rpcConfig"; - -// Network configuration with logos -import arbitrumLogo from "../../assets/arbitrum-logo.svg"; -import optimismLogo from "../../assets/optimism-logo.svg"; -import baseLogo from "../../assets/base-logo.svg"; -import hardhatLogo from "../../assets/hardhat-logo.svg"; - -interface NetworkInfo { - name: string; - logo: string | null; - color: string; -} - -const NETWORK_INFO: Record = { - 1: { name: "Ethereum", logo: null, color: "#627EEA" }, - 11155111: { name: "Sepolia", logo: null, color: "#F0CDC2" }, - 42161: { name: "Arbitrum", logo: arbitrumLogo, color: "#28A0F0" }, - 10: { name: "Optimism", logo: optimismLogo, color: "#FF0420" }, - 8453: { name: "Base", logo: baseLogo, color: "#0052FF" }, - 56: { name: "BSC", logo: "bsc", color: "#F0B90B" }, - 97: { name: "BSC Testnet", logo: "bsc", color: "#F0B90B" }, - 137: { name: "Polygon", logo: "polygon", color: "#8247E5" }, - 31337: { name: "Localhost", logo: hardhatLogo, color: "#FFF100" }, -}; - -// Ethereum SVG for networks without a logo -const EthereumIcon = ({ color }: { color: string }) => ( - - - - - - -); - -// BSC/BNB SVG icon -const BscIcon = ({ - color, - opacity = 1, -}: { - color: string; - opacity?: number; -}) => ( - - - - -); - -// Polygon SVG icon -const PolygonIcon = ({ color }: { color: string }) => ( - - - -); - -interface NetworkBlockIndicatorProps { - className?: string; -} - -export function NetworkBlockIndicator({ - className, -}: NetworkBlockIndicatorProps) { - const location = useLocation(); - const { rpcUrls } = useContext(AppContext); - const [blockNumber, setBlockNumber] = useState(null); - const [isLoading, setIsLoading] = useState(false); - - // Extract chainId from the pathname (e.g., /1/blocks -> 1) - const chainId = useMemo(() => { - const pathSegments = location.pathname.split("/").filter(Boolean); - return pathSegments[0] && !isNaN(Number(pathSegments[0])) - ? Number(pathSegments[0]) - : null; - }, [location.pathname]); - - const networkInfo = chainId ? NETWORK_INFO[chainId] : null; - - useEffect(() => { - if (!chainId) { - setBlockNumber(null); - return; - } - - let isMounted = true; - let intervalId: NodeJS.Timeout | null = null; - - const fetchBlockNumber = async () => { - try { - const urls = getRPCUrls(chainId, rpcUrls); - const client = new RPCClient(urls); - const result = await client.call("eth_blockNumber", []); - if (isMounted) { - setBlockNumber(parseInt(result, 16)); - setIsLoading(false); - } - } catch (error) { - console.error("Failed to fetch block number:", error); - if (isMounted) { - setIsLoading(false); - } - } - }; - - setIsLoading(true); - fetchBlockNumber(); - - // Poll for new blocks every 12 seconds (Ethereum average block time) - intervalId = setInterval(fetchBlockNumber, 12000); - - return () => { - isMounted = false; - if (intervalId) { - clearInterval(intervalId); - } - }; - }, [chainId, rpcUrls]); - - if (!chainId || !networkInfo) return null; - - return ( -
-
-
- {networkInfo.logo === "bsc" ? ( - - ) : networkInfo.logo === "polygon" ? ( - - ) : networkInfo.logo ? ( - {networkInfo.name} - ) : ( - - )} -
- - {isLoading - ? "..." - : blockNumber !== null - ? `#${blockNumber.toLocaleString()}` - : "---"} - -
- ); -} - -export default NetworkBlockIndicator; diff --git a/src/components/common/NetworkIcon.tsx b/src/components/common/NetworkIcon.tsx index d4b8ed8..9a73ee4 100644 --- a/src/components/common/NetworkIcon.tsx +++ b/src/components/common/NetworkIcon.tsx @@ -1,116 +1,44 @@ -import React from "react"; -import arbitrumLogo from "../../assets/arbitrum-logo.svg"; -import optimismLogo from "../../assets/optimism-logo.svg"; -import baseLogo from "../../assets/base-logo.svg"; -import hardhatLogo from "../../assets/hardhat-logo.svg"; -import type { NetworkConfig } from "../../config/networks"; +import type React from "react"; +import { useState } from "react"; +import { getNetworkLogoUrl } from "../../services/MetadataService"; +import type { NetworkConfig } from "../../types"; interface NetworkIconProps { - network: NetworkConfig; - size?: number; + network: NetworkConfig; + size?: number; } -// Ethereum SVG icon -const EthereumIcon: React.FC<{ color: string; size: number }> = ({ - color, - size, -}) => ( - - - - - - +// Fallback Ethereum SVG icon (used when logo fails to load) +const EthereumIcon: React.FC<{ color: string; size: number }> = ({ color, size }) => ( + // biome-ignore lint/a11y/noSvgWithoutTitle: + + + + + + ); -// BSC/BNB SVG icon -const BscIcon: React.FC<{ color: string; size: number; opacity?: number }> = ({ - color, - size, - opacity = 1, -}) => ( - - - - -); +export const NetworkIcon: React.FC = ({ network, size = 32 }) => { + const [imageError, setImageError] = useState(false); -// Polygon SVG icon -const PolygonIcon: React.FC<{ color: string; size: number }> = ({ - color, - size, -}) => ( - - - -); + // If logo failed to load, show fallback + if (imageError || !network.logo) { + return ; + } + + const logoUrl = getNetworkLogoUrl(network.logo); -export const NetworkIcon: React.FC = ({ - network, - size = 32, -}) => { - switch (network.logoType) { - case "ethereum": - return ; - case "bsc": - return ( - - ); - case "polygon": - return ; - case "arbitrum": - return ( - Arbitrum - ); - case "optimism": - return ( - Optimism - ); - case "base": - return Base; - case "hardhat": - return ( - Localhost - ); - default: - return ; - } + return ( + {network.name} setImageError(true)} + className="object-contain" + /> + ); }; export default NetworkIcon; diff --git a/src/components/common/NetworkStatsDisplay.tsx b/src/components/common/NetworkStatsDisplay.tsx deleted file mode 100644 index 4912fde..0000000 --- a/src/components/common/NetworkStatsDisplay.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import React from "react"; -import type { NetworkStats, RPCMetadata } from "../../types"; -import { RPCIndicator } from "./RPCIndicator"; - -interface NetworkStatsDisplayProps { - networkStats: NetworkStats | null; - loading?: boolean; - error?: string | null; - chainId?: number; - metadata?: RPCMetadata; - selectedProvider?: string | null; - onProviderSelect?: (provider: string) => void; -} - -const NetworkStatsDisplay: React.FC = React.memo( - ({ - networkStats, - loading = false, - error = null, - chainId, - metadata, - selectedProvider, - onProviderSelect, - }) => { - if (loading) { - return ( -
-
-
- Loading network statistics... -
-
-
- ); - } - - if (error) { - return ( -
-
-
- Error loading network stats: {error} -
-
-
- ); - } - - if (!networkStats) { - return null; - } - - // Format gas price from Wei to Gwei - const formatGasPrice = (weiPrice: string): string => { - try { - const gwei = Number(weiPrice) / 1e9; - return `${gwei.toFixed(2)} Gwei`; - } catch { - return weiPrice; - } - }; - - // Format block number with commas - const formatBlockNumber = (blockNumber: string): string => { - try { - return Number(blockNumber).toLocaleString(); - } catch { - return blockNumber; - } - }; - - // Parse protocol version from metadata (localhost/Hardhat only) - const getProtocolVersion = (): string | null => { - if (chainId !== 31337 || !networkStats.metadata) { - return null; - } - - try { - return networkStats.metadata.clientVersion || null; - } catch (err) { - console.error("Failed to parse metadata:", err); - return null; - } - }; - - // Get forked network info (localhost/Hardhat only) - const getForkedNetworkInfo = (): { - chainId: number; - blockNumber: number; - blockHash: string; - } | null => { - if ( - chainId !== 31337 || - !networkStats.metadata || - !networkStats.metadata.forkedNetwork - ) { - return null; - } - - try { - const forked = networkStats.metadata.forkedNetwork; - return { - chainId: forked.chainId, - blockNumber: forked.forkBlockNumber, - blockHash: forked.forkBlockHash, - }; - } catch (err) { - console.error("Failed to parse forked network info:", err); - return null; - } - }; - - const protocolVersion = getProtocolVersion(); - const forkedNetwork = getForkedNetworkInfo(); - - return ( -
-
-
-

- Network Statistics -

- {metadata && selectedProvider !== undefined && onProviderSelect && ( - - )} -
- -
-
- Current Gas Price - - {formatGasPrice(networkStats.currentGasPrice)} - -
- -
- Current Block Number - - {formatBlockNumber(networkStats.currentBlockNumber)} - -
- -
- Sync Status - - - - {networkStats.isSyncing ? "Syncing" : "Synced"} - - -
- - {networkStats.clientVersion && ( -
- Client Version - - {networkStats.clientVersion} - -
- )} - - {protocolVersion && ( -
- Protocol Version - {protocolVersion} -
- )} - - {forkedNetwork && ( - <> -
- Forked Network - - Chain ID: {forkedNetwork.chainId} - -
- -
- Fork Block Number - - {formatBlockNumber(forkedNetwork.blockNumber.toString())} - -
- -
- Fork Block Hash - - {forkedNetwork.blockHash} - -
- - )} -
-
-
- ); - }, -); - -NetworkStatsDisplay.displayName = "NetworkStatsDisplay"; - -export default NetworkStatsDisplay; diff --git a/src/components/common/NotificationDisplay.tsx b/src/components/common/NotificationDisplay.tsx index 0980fc8..4eab1a8 100644 --- a/src/components/common/NotificationDisplay.tsx +++ b/src/components/common/NotificationDisplay.tsx @@ -2,35 +2,31 @@ import React from "react"; import { useNotifications } from "../../context/NotificationContext"; const NotificationDisplay: React.FC = React.memo(() => { - const { notifications, removeNotification } = useNotifications(); + const { notifications, removeNotification } = useNotifications(); - if (notifications.length === 0) { - return null; - } + if (notifications.length === 0) { + return null; + } - return ( -
- {notifications.map((notification) => ( -
-
- - {notification.message} - - -
-
- ))} -
- ); + return ( +
+ {notifications.map((notification) => ( +
+
+ {notification.message} + {/** biome-ignore lint/a11y/useButtonType: */} + +
+
+ ))} +
+ ); }); NotificationDisplay.displayName = "NotificationDisplay"; diff --git a/src/components/common/RPCIndicator.tsx b/src/components/common/RPCIndicator.tsx index 9475f1a..52a73c8 100644 --- a/src/components/common/RPCIndicator.tsx +++ b/src/components/common/RPCIndicator.tsx @@ -1,11 +1,11 @@ -import { useState, useRef, useEffect } from "react"; +import { useEffect, useRef, useState } from "react"; import type { RPCMetadata } from "../../types"; interface RPCIndicatorProps { - metadata: RPCMetadata; - selectedProvider: string | null; - onProviderSelect: (url: string) => void; - className?: string; + metadata: RPCMetadata; + selectedProvider: string | null; + onProviderSelect: (url: string) => void; + className?: string; } /** @@ -13,131 +13,117 @@ interface RPCIndicatorProps { * Expands on click to show detailed provider information */ export function RPCIndicator({ - metadata, - selectedProvider, - onProviderSelect, - className, + metadata, + selectedProvider, + onProviderSelect, + className, }: RPCIndicatorProps) { - const [isExpanded, setIsExpanded] = useState(false); - const dropdownRef = useRef(null); + const [isExpanded, setIsExpanded] = useState(false); + const dropdownRef = useRef(null); - const successCount = metadata.responses.filter( - (r) => r.status === "success", - ).length; - const totalCount = metadata.responses.length; + const successCount = metadata.responses.filter((r) => r.status === "success").length; + const totalCount = metadata.responses.length; - // Close dropdown when clicking outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - dropdownRef.current && - !dropdownRef.current.contains(event.target as Node) - ) { - setIsExpanded(false); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsExpanded(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); - return ( -
- {/* Compact Badge */} -
setIsExpanded(!isExpanded)} - title="Click to see RPC provider details" - > - {metadata.hasInconsistencies && ( - - āš ļø - - )} - - āœ“ {successCount}/{totalCount} - -
+ return ( +
+ {/* Compact Badge */} + {/** biome-ignore lint/a11y/noStaticElementInteractions: */} + {/** biome-ignore lint/a11y/useKeyWithClickEvents: */} +
setIsExpanded(!isExpanded)} + title="Click to see RPC provider details" + > + {metadata.hasInconsistencies && ( + + āš ļø + + )} + + āœ“ {successCount}/{totalCount} + +
- {/* Expanded Dropdown */} - {isExpanded && ( -
-
- RPC Providers - - Strategy: {metadata.strategy} - -
+ {/* Expanded Dropdown */} + {isExpanded && ( +
+
+ RPC Providers + Strategy: {metadata.strategy} +
- {metadata.hasInconsistencies && ( -
- āš ļø Responses differ between providers -
- )} + {metadata.hasInconsistencies && ( +
āš ļø Responses differ between providers
+ )} -
- {metadata.responses.map((response, idx) => { - const isSelected = selectedProvider === response.url; - const urlDisplay = truncateUrl(response.url); +
+ {metadata.responses.map((response, idx) => { + const isSelected = selectedProvider === response.url; + const urlDisplay = truncateUrl(response.url); - return ( -
{ - if (response.status === "success") { - onProviderSelect(response.url); - setIsExpanded(false); - } - }} - > -
- #{idx + 1} - - {urlDisplay} - - - {response.status === "success" ? "āœ“" : "āœ—"} - -
+ return ( + // biome-ignore lint/a11y/noStaticElementInteractions: + // biome-ignore lint/a11y/useKeyWithClickEvents: +
{ + if (response.status === "success") { + onProviderSelect(response.url); + setIsExpanded(false); + } + }} + > +
+ #{idx + 1} + + {urlDisplay} + + + {response.status === "success" ? "āœ“" : "āœ—"} + +
- {response.status === "error" && ( -
- {response.error} -
- )} + {response.status === "error" && ( +
{response.error}
+ )} - {isSelected && ( -
Selected
- )} -
- ); - })} -
-
- )} -
- ); + {isSelected &&
Selected
} +
+ ); + })} +
+
+ )} +
+ ); } /** * Truncate URL to show hostname only */ function truncateUrl(url: string): string { - try { - const urlObj = new URL(url); - const hostname = urlObj.hostname; - if (hostname.length > 30) { - return hostname.slice(0, 15) + "..." + hostname.slice(-12); - } - return hostname; - } catch { - return url.length > 30 ? url.slice(0, 15) + "..." + url.slice(-12) : url; - } + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname; + if (hostname.length > 30) { + return `${hostname.slice(0, 15)}...${hostname.slice(-12)}`; + } + return hostname; + } catch { + return url.length > 30 ? `${url.slice(0, 15)}...${url.slice(-12)}` : url; + } } export default RPCIndicator; diff --git a/src/components/common/SearchBox.tsx b/src/components/common/SearchBox.tsx index 6272432..28899c3 100644 --- a/src/components/common/SearchBox.tsx +++ b/src/components/common/SearchBox.tsx @@ -1,56 +1,29 @@ -import React, { useState } from "react"; -import { useLocation, useNavigate } from "react-router-dom"; +import { useSearch } from "../../hooks/useSearch"; const SearchBox = () => { - const [searchTerm, setSearchTerm] = useState(""); - const navigate = useNavigate(); - const location = useLocation(); + const { searchTerm, setSearchTerm, isResolving, error, clearError, handleSearch } = useSearch(); - // Extract chainId from the pathname (e.g., /1/blocks -> 1) - const pathSegments = location.pathname.split("/").filter(Boolean); - const chainId = - pathSegments[0] && !isNaN(Number(pathSegments[0])) - ? pathSegments[0] - : undefined; - - const handleSearch = (e: React.FormEvent) => { - e.preventDefault(); - if (!searchTerm.trim()) return; - - // Basic routing logic placeholder - // In a real app, we would detect the type of input (address, tx, block) - // For now, we'll just log it or maybe navigate to a search result page if it existed. - // Since the user just asked for the UI, we'll keep the logic minimal but functional enough to show interaction. - console.log("Searching for:", searchTerm); - - // Example logic: - if (searchTerm.startsWith("0x")) { - if (searchTerm.length === 42) { - navigate(`/${chainId}/address/${searchTerm}`); - } else if (searchTerm.length === 66) { - navigate(`/${chainId}/tx/${searchTerm}`); - } - } else if (!isNaN(Number(searchTerm))) { - navigate(`/${chainId}/block/${searchTerm}`); - } - }; - - return ( -
-
- setSearchTerm(e.target.value)} - /> - -
-
- ); + return ( +
+
+ { + setSearchTerm(e.target.value); + clearError(); + }} + disabled={isResolving} + /> + +
+ {error &&
{error}
} +
+ ); }; export default SearchBox; diff --git a/src/components/common/TierBadge.tsx b/src/components/common/TierBadge.tsx new file mode 100644 index 0000000..580c132 --- /dev/null +++ b/src/components/common/TierBadge.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { getSubscriptionTierName, isSubscriptionActive } from "../../services/MetadataService"; +import type { NetworkSubscription } from "../../types"; + +interface TierBadgeProps { + subscription: NetworkSubscription | undefined; + /** Size variant */ + size?: "small" | "medium"; + /** Whether to show "Subscriber" suffix */ + showSuffix?: boolean; + /** Additional CSS class */ + className?: string; +} + +/** + * Displays a subscription tier badge (Backer, Partner, or Ally) + * Only renders if the subscription is active + */ +const TierBadge: React.FC = React.memo( + ({ subscription, size = "medium", showSuffix = false, className = "" }) => { + if (!subscription || !isSubscriptionActive(subscription)) { + return null; + } + + const tierName = getSubscriptionTierName(subscription.tier); + const sizeClass = size === "small" ? "tier-badge-small" : "tier-badge-medium"; + + return ( + + {tierName} + {showSuffix} + + ); + }, +); + +TierBadge.displayName = "TierBadge"; + +export default TierBadge; diff --git a/src/components/common/TransactionDisplay.tsx b/src/components/common/TransactionDisplay.tsx deleted file mode 100644 index 260b6c3..0000000 --- a/src/components/common/TransactionDisplay.tsx +++ /dev/null @@ -1,773 +0,0 @@ -import React, { - useState, - useEffect, - useRef, - useMemo, - useCallback, -} from "react"; -import { Link } from "react-router-dom"; -import type { - Transaction, - TransactionArbitrum, - TransactionReceiptArbitrum, - TransactionReceiptOptimism, - RPCMetadata, -} from "../../types"; -import LongString from "./LongString"; -import { DataService } from "../../services/DataService"; -import { TraceResult } from "../../services/EVM/L1/fetchers/trace"; -import { - decodeEventLog, - DecodedEvent, - formatDecodedValue, - getEventTypeColor, -} from "../../utils/eventDecoder"; -import { RPCIndicator } from "./RPCIndicator"; - -interface TransactionDisplayProps { - transaction: Transaction | TransactionArbitrum; - chainId?: string; - currentBlockNumber?: number; - dataService?: DataService; - metadata?: RPCMetadata; - selectedProvider?: string | null; - onProviderSelect?: (provider: string) => void; -} - -const TransactionDisplay: React.FC = React.memo( - ({ - transaction, - chainId, - currentBlockNumber, - dataService, - metadata, - selectedProvider, - onProviderSelect, - }) => { - const [showRawData, setShowRawData] = useState(false); - const [showLogs, setShowLogs] = useState(false); - const [showTrace, setShowTrace] = useState(false); - const [traceData, setTraceData] = useState(null); - const [callTrace, setCallTrace] = useState(null); - const [loadingTrace, setLoadingTrace] = useState(false); - const [copiedField, setCopiedField] = useState(null); - const copyTimeoutRef = useRef | null>(null); - - // Check if trace is available (localhost only) - const isTraceAvailable = dataService?.isTraceAvailable() || false; - - // Load trace data when trace section is expanded - useEffect(() => { - if ( - showTrace && - isTraceAvailable && - dataService && - !traceData && - !callTrace - ) { - setLoadingTrace(true); - Promise.all([ - dataService.getTransactionTrace(transaction.hash), - dataService.getCallTrace(transaction.hash), - ]) - .then(([trace, call]) => { - setTraceData(trace); - setCallTrace(call); - }) - .catch((err) => console.error("Error loading trace:", err)) - .finally(() => setLoadingTrace(false)); - } - }, [ - showTrace, - isTraceAvailable, - dataService, - transaction.hash, - traceData, - callTrace, - ]); - - useEffect(() => { - return () => { - if (copyTimeoutRef.current) { - clearTimeout(copyTimeoutRef.current); - } - }; - }, []); - - // Check if this is an Arbitrum transaction - const isArbitrumTx = useCallback( - (tx: Transaction | TransactionArbitrum): tx is TransactionArbitrum => { - return "requestId" in tx; - }, - [], - ); - - // Check if receipt is Arbitrum receipt - const isArbitrumReceipt = useCallback( - (receipt: any): receipt is TransactionReceiptArbitrum => { - return receipt && "l1BlockNumber" in receipt; - }, - [], - ); - - // Check if receipt is Optimism receipt - const isOptimismReceipt = useCallback( - (receipt: any): receipt is TransactionReceiptOptimism => { - return receipt && "l1Fee" in receipt; - }, - [], - ); - - const truncate = useCallback((str: string, start = 6, end = 4) => { - if (!str) return ""; - if (str.length <= start + end) return str; - return `${str.slice(0, start)}...${str.slice(-end)}`; - }, []); - - const formatValue = useCallback((value: string) => { - try { - const eth = Number(value) / 1e18; - return `${eth.toFixed(6)} ETH`; - } catch (e) { - return value; - } - }, []); - - const formatGwei = useCallback((value: string) => { - try { - const gwei = Number(value) / 1e9; - return `${gwei.toFixed(2)} Gwei`; - } catch (e) { - return value; - } - }, []); - - const parseTimestampToMs = useCallback((timestamp?: string) => { - if (!timestamp) return null; - const parsed = parseInt(timestamp, timestamp.startsWith("0x") ? 16 : 10); - if (Number.isNaN(parsed)) return null; - return parsed * 1000; - }, []); - - const formatAbsoluteTimestamp = useCallback((timestampMs: number) => { - try { - return new Intl.DateTimeFormat(undefined, { - year: "numeric", - month: "short", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - timeZoneName: "short", - }).format(new Date(timestampMs)); - } catch (error) { - return new Date(timestampMs).toISOString(); - } - }, []); - - const formatTimeAgo = useCallback((timestampMs: number) => { - const diffMs = Date.now() - timestampMs; - const diffSeconds = Math.floor(Math.abs(diffMs) / 1000); - if (diffSeconds < 5) { - return diffMs >= 0 ? "just now" : "in a few seconds"; - } - - const units = [ - { label: "day", seconds: 60 * 60 * 24 }, - { label: "hour", seconds: 60 * 60 }, - { label: "minute", seconds: 60 }, - ]; - - for (const unit of units) { - if (diffSeconds >= unit.seconds) { - const value = Math.floor(diffSeconds / unit.seconds); - const plural = value === 1 ? "" : "s"; - return diffMs >= 0 - ? `${value} ${unit.label}${plural} ago` - : `in ${value} ${unit.label}${plural}`; - } - } - - return diffMs >= 0 - ? `${diffSeconds} second${diffSeconds === 1 ? "" : "s"} ago` - : `in ${diffSeconds} second${diffSeconds === 1 ? "" : "s"}`; - }, []); - - const timestampMs = useMemo( - () => parseTimestampToMs(transaction.timestamp), - [parseTimestampToMs, transaction.timestamp], - ); - const formattedTimestamp = useMemo( - () => (timestampMs ? formatAbsoluteTimestamp(timestampMs) : null), - [timestampMs, formatAbsoluteTimestamp], - ); - const timestampAge = useMemo( - () => (timestampMs ? formatTimeAgo(timestampMs) : null), - [timestampMs, formatTimeAgo], - ); - - const getStatusBadge = useCallback((status?: string) => { - if (!status) return null; - const isSuccess = status === "0x1" || status === "1"; - return ( - - {isSuccess ? "Success" : "Failed"} - - ); - }, []); - - const confirmations = useMemo( - () => - currentBlockNumber - ? currentBlockNumber - Number(transaction.blockNumber) - : null, - [currentBlockNumber, transaction.blockNumber], - ); - - return ( -
-
- Transaction Details - {metadata && selectedProvider !== undefined && onProviderSelect && ( - - )} -
- - {/* Row-based layout like Etherscan */} -
- {/* Transaction Hash */} -
- Transaction Hash: - - - -
- - {/* Status */} -
- Status: - - {getStatusBadge(transaction.receipt?.status)} - -
- - {/* Block */} -
- Block: - - {chainId ? ( - - {Number(transaction.blockNumber).toLocaleString()} - - ) : ( - Number(transaction.blockNumber).toLocaleString() - )} - {confirmations !== null && ( - - {confirmations > 100 - ? "+100" - : confirmations.toLocaleString()}{" "} - Block Confirmations - - )} - -
- - {/* Timestamp */} - {formattedTimestamp && ( -
- Timestamp: - - {timestampAge && {timestampAge}} - - ({formattedTimestamp}) - - -
- )} - - {/* From */} -
- From: - - {chainId ? ( - - {transaction.from} - - ) : ( - transaction.from - )} - -
- - {/* To */} -
- - {transaction.to ? "To:" : "Interacted With:"} - - - {transaction.to ? ( - chainId ? ( - - {transaction.to} - - ) : ( - transaction.to - ) - ) : ( - - Contract Creation - - )} - -
- - {/* Contract Address (if created) */} - {transaction.receipt?.contractAddress && ( -
- Contract Created: - - {chainId ? ( - - {transaction.receipt.contractAddress} - - ) : ( - transaction.receipt.contractAddress - )} - -
- )} - - {/* Value */} -
- Value: - - {formatValue(transaction.value)} - -
- - {/* Transaction Fee */} -
- Transaction Fee: - - {transaction.receipt - ? formatValue( - ( - BigInt(transaction.receipt.gasUsed) * - BigInt(transaction.receipt.effectiveGasPrice) - ).toString(), - ) - : "Pending"} - -
- - {/* Gas Price */} -
- Gas Price: - {formatGwei(transaction.gasPrice)} -
- - {/* Gas Limit & Usage */} -
- Gas Limit & Usage: - - {Number(transaction.gas).toLocaleString()} - {transaction.receipt && ( - <> - {" | "} - {Number(transaction.receipt.gasUsed).toLocaleString()} - - ( - {( - (Number(transaction.receipt.gasUsed) / - Number(transaction.gas)) * - 100 - ).toFixed(1)} - %) - - - )} - -
- - {/* Effective Gas Price (if different from gas price) */} - {transaction.receipt && - transaction.receipt.effectiveGasPrice !== transaction.gasPrice && ( -
- Effective Gas Price: - - {formatGwei(transaction.receipt.effectiveGasPrice)} - -
- )} - - {/* Arbitrum-specific fields */} - {isArbitrumTx(transaction) && - transaction.receipt && - isArbitrumReceipt(transaction.receipt) && ( - <> -
- L1 Block Number: - - {Number(transaction.receipt.l1BlockNumber).toLocaleString()} - -
-
- Gas Used for L1: - - {Number(transaction.receipt.gasUsedForL1).toLocaleString()} - -
- - )} - - {/* OP Stack fields (Optimism, Base) */} - {transaction.receipt && isOptimismReceipt(transaction.receipt) && ( - <> -
- L1 Fee: - - {formatValue(transaction.receipt.l1Fee)} - -
-
- L1 Gas Price: - - {formatGwei(transaction.receipt.l1GasPrice)} - -
-
- L1 Gas Used: - - {Number(transaction.receipt.l1GasUsed).toLocaleString()} - -
-
- L1 Fee Scalar: - - {transaction.receipt.l1FeeScalar} - -
- - )} - - {/* Other Attributes (Nonce, Index, Type) */} -
- Other Attributes: - - Nonce: {transaction.nonce} - - Position: {transaction.transactionIndex} - - Type: {transaction.type} - -
- - {/* Input Data */} -
- Input Data: - {transaction.data && transaction.data !== "0x" ? ( -
- {transaction.data} -
- ) : ( - 0x - )} -
-
- - {/* Event Logs Section - Always visible */} - {transaction.receipt && transaction.receipt.logs.length > 0 && ( -
-
- - Event Logs ({transaction.receipt.logs.length}) - -
-
- {transaction.receipt.logs.map((log: any, index: number) => { - const decoded: DecodedEvent | null = log.topics - ? decodeEventLog(log.topics, log.data || "0x") - : null; - - return ( -
-
{index}
-
- {/* Decoded Event Header */} - {decoded && ( -
- - {decoded.name} - - - {decoded.fullSignature} - -
- )} - - {/* Address */} -
- Address - - {chainId ? ( - - {log.address} - - ) : ( - log.address - )} - -
- - {/* Decoded Parameters */} - {decoded && decoded.params.length > 0 && ( -
- Decoded -
- {decoded.params.map((param, i) => ( -
- - {param.name} - - - ({param.type}) - - - {param.type === "address" && chainId ? ( - - {param.value} - - ) : ( - formatDecodedValue(param.value, param.type) - )} - - {param.indexed && ( - - indexed - - )} -
- ))} -
-
- )} - - {/* Raw Topics (collapsed if decoded) */} - {log.topics && log.topics.length > 0 && ( -
- - {decoded ? "Raw Topics" : "Topics"} - -
- {log.topics.map((topic: string, i: number) => ( -
- [{i}] - {topic} -
- ))} -
-
- )} - - {/* Raw Data */} - {log.data && log.data !== "0x" && ( -
- - {decoded ? "Raw Data" : "Data"} - -
- {log.data} -
-
- )} -
-
- ); - })} -
-
- )} - - {/* Debug Trace Section (Localhost Only) */} - {isTraceAvailable && ( -
- - - {showTrace && ( -
- {loadingTrace && ( -
Loading trace data...
- )} - - {/* Call Trace */} - {callTrace && ( -
-
Call Trace
-
-
- Type:{" "} - {callTrace.type} -
-
- From:{" "} - -
-
- To:{" "} - -
-
- Value:{" "} - {callTrace.value} -
-
- Gas: {callTrace.gas} -
-
- Gas Used:{" "} - {callTrace.gasUsed} -
- {callTrace.error && ( -
- Error:{" "} - {callTrace.error} -
- )} - {callTrace.calls && callTrace.calls.length > 0 && ( -
-
- Internal Calls ({callTrace.calls.length}): -
-
- {JSON.stringify(callTrace.calls, null, 2)} -
-
- )} -
-
- )} - - {/* Opcode Trace */} - {traceData && ( -
-
Execution Trace
-
-
- Total Gas Used:{" "} - {traceData.gas} -
-
- Failed:{" "} - {traceData.failed ? "Yes" : "No"} -
-
- Return Value:{" "} - -
-
- Opcodes Executed:{" "} - {traceData.structLogs.length} -
-
- -
- Opcode Execution Log: -
-
- {traceData.structLogs.slice(0, 100).map((log, index) => ( -
-
- Step {index}: {log.op} -
-
- PC: {log.pc} | Gas: {log.gas} | Cost: {log.gasCost}{" "} - | Depth: {log.depth} -
- {log.stack && log.stack.length > 0 && ( -
- Stack: [{log.stack.slice(0, 3).join(", ")} - {log.stack.length > 3 ? "..." : ""}] -
- )} -
- ))} - {traceData.structLogs.length > 100 && ( -
- ... showing first 100 of {traceData.structLogs.length}{" "} - steps -
- )} -
-
- )} -
- )} -
- )} -
- ); - }, -); - -TransactionDisplay.displayName = "TransactionDisplay"; - -export default TransactionDisplay; diff --git a/src/components/common/TransactionReceiptDisplay.tsx b/src/components/common/TransactionReceiptDisplay.tsx deleted file mode 100644 index c926c6e..0000000 --- a/src/components/common/TransactionReceiptDisplay.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from "react"; -import { TransactionReceipt } from "../../types"; - -interface TransactionReceiptDisplayProps { - receipt: TransactionReceipt; -} - -const TransactionReceiptDisplay: React.FC = ({ - receipt, -}) => { - const truncate = (str: string, start = 6, end = 4) => { - if (!str) return ""; - if (str.length <= start + end) return str; - return `${str.slice(0, start)}...${str.slice(-end)}`; - }; - - const getStatusText = (status: string) => { - return status === "1" || status === "0x1" ? "Success" : "Failed"; - }; - - return ( -
-
- Receipt: - - {truncate(receipt.transactionHash, 8, 6)} - -
- -
-
- Status - {getStatusText(receipt.status)} -
- -
- Block Number - {receipt.blockNumber} -
- -
- From - - {truncate(receipt.from)} - -
- -
- To - - {truncate(receipt.to)} - -
- -
- Gas Used - - {Number(receipt.gasUsed).toLocaleString()} - -
- -
- Cumulative Gas Used - - {Number(receipt.cumulativeGasUsed).toLocaleString()} - -
- -
- Contract Address - - {receipt.contractAddress || "N/A"} - -
-
-
- ); -}; - -export default TransactionReceiptDisplay; diff --git a/src/components/common/VersionWarningIcon.tsx b/src/components/common/VersionWarningIcon.tsx deleted file mode 100644 index 4969090..0000000 --- a/src/components/common/VersionWarningIcon.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState, useMemo } from "react"; -import { ENVIRONMENT } from "../../utils/constants"; - -interface VersionWarningIconProps { - readonly className?: string; -} - -// Constants outside component to avoid recreating -const TOOLTIP_TEXT = "This is not a production verified version"; -const ICON_COLORS = { - development: "#ef4444", - staging: "#f59e0b", - default: "#6b7280" -} as const; - -const VersionWarningIcon: React.FC = ({ className = "" }) => { - const [showTooltip, setShowTooltip] = useState(false); - - // Memoize color calculation - const iconColor = useMemo(() => { - return ICON_COLORS[ENVIRONMENT as keyof typeof ICON_COLORS] || ICON_COLORS.default; - }, []); - - if (ENVIRONMENT === "production") { - return null; - } - - return ( -
setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - > - - - - {showTooltip && ( -
- {TOOLTIP_TEXT} -
- )} -
- ); -}; - -export default VersionWarningIcon; \ No newline at end of file diff --git a/src/components/common/index.ts b/src/components/common/index.ts deleted file mode 100644 index ccf01bd..0000000 --- a/src/components/common/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as ErrorBoundary } from "./ErrorBoundary"; -export { default as Loading } from "./Loading"; -export { default as Navbar } from "./Navbar"; -export { default as NetworkBlockIndicator } from "./NetworkBlockIndicator"; -export { default as NotificationDisplay } from "./NotificationDisplay"; diff --git a/src/components/common/modal/BaseModal.tsx b/src/components/common/modal/BaseModal.tsx deleted file mode 100644 index 7316c03..0000000 --- a/src/components/common/modal/BaseModal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { ReactNode } from "react"; -import "../../../styles/styles.css"; - -export interface BaseModalProps { - isOpen: boolean; - onClose: () => void; - title: string; - children: ReactNode; - isLoading?: boolean; - size?: "sm" | "md" | "lg"; - closeOnOverlayClick?: boolean; -} - -export default function BaseModal({ - isOpen, - onClose, - title, - children, - isLoading = false, - size = "md", - closeOnOverlayClick = true, -}: BaseModalProps) { - if (!isOpen) return null; - - const handleOverlayClick = (e: React.MouseEvent) => { - if (closeOnOverlayClick && !isLoading) { - onClose(); - } - }; - - const handleContentClick = (e: React.MouseEvent) => { - e.stopPropagation(); - }; - - const getSizeClass = () => { - switch (size) { - case "sm": - return "modal-sm"; - case "lg": - return "modal-lg"; - default: - return ""; - } - }; - - return ( -
-
-
-

{title}

- -
- -
{children}
-
-
- ); -} diff --git a/src/components/common/modal/index.tsx b/src/components/common/modal/index.tsx deleted file mode 100644 index 6dd177a..0000000 --- a/src/components/common/modal/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Modal Components System - * - * This folder contains all modal-related components for the application. - * The system is designed with reusability and consistency in mind. - * - * Architecture: - * - BaseModal: Core modal wrapper with overlay, title, close button - * - ModalActions: Reusable action button layouts - * - ModalLoading: Standard loading animation component - * - Specific Modals: ActionModal, NewTokenDepositModal, DiscoverTokensModal - * - * Usage Examples: - * - * 1. Using BaseModal for custom modals: - * ```tsx - * import { BaseModal, StandardActions } from '../common/modal'; - * - * - *

Custom content here

- * - *
- * ``` - * - * 2. Using specific modals: - * ```tsx - * import { ActionModal, DiscoverTokensModal } from '../common/modal'; - * ``` - * - * Features: - * - Consistent styling across all modals - * - Reusable action buttons and loading states - * - Accessibility support (ESC key, focus management) - * - Dark mode support through CSS variables - * - Responsive design - */ - -// Base modal components -export { default as BaseModal } from "./BaseModal"; -export type { BaseModalProps } from "./BaseModal"; diff --git a/src/components/devtools/ContractsSection.tsx b/src/components/devtools/ContractsSection.tsx deleted file mode 100644 index a139b3c..0000000 --- a/src/components/devtools/ContractsSection.tsx +++ /dev/null @@ -1,541 +0,0 @@ -import React, { useState } from "react"; - -const NETWORKS: Record = { - 1: { name: "Ethereum Mainnet" }, - 10: { name: "Optimism" }, - 42161: { name: "Arbitrum One" }, - 8453: { name: "Base" }, - 137: { name: "Polygon" }, - 11155111: { name: "Sepolia" }, - 84532: { name: "Base Sepolia" }, -}; - -const SOURCIFY_API = "https://sourcify.dev/server"; - -const ContractsSection: React.FC = () => { - // Standard JSON Verification - const [showStandardVerify, setShowStandardVerify] = useState(true); - const [stdChainId, setStdChainId] = useState(1); - const [stdAddress, setStdAddress] = useState(""); - const [stdContractName, setStdContractName] = useState(""); - const [stdSources, setStdSources] = useState(JSON.stringify({ - "MyContract.sol": { - content: "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract MyContract {\n uint256 public value;\n \n constructor(uint256 _value) {\n value = _value;\n }\n}" - } - }, null, 2)); - const [stdSettings, setStdSettings] = useState(JSON.stringify({ - optimizer: { - enabled: true, - runs: 200 - }, - evmVersion: "paris" - }, null, 2)); - const [stdVerifying, setStdVerifying] = useState(false); - const [stdResult, setStdResult] = useState(null); - const [stdError, setStdError] = useState(null); - - // Metadata Verification - const [showMetadataVerify, setShowMetadataVerify] = useState(false); - const [metaChainId, setMetaChainId] = useState(1); - const [metaAddress, setMetaAddress] = useState(""); - const [metadataJson, setMetadataJson] = useState(""); - const [metaVerifying, setMetaVerifying] = useState(false); - const [metaResult, setMetaResult] = useState(null); - const [metaError, setMetaError] = useState(null); - - // Etherscan Import - const [showEtherscanImport, setShowEtherscanImport] = useState(false); - const [etherscanChainId, setEtherscanChainId] = useState(1); - const [etherscanAddress, setEtherscanAddress] = useState(""); - const [etherscanApiKey, setEtherscanApiKey] = useState(""); - const [etherscanImporting, setEtherscanImporting] = useState(false); - const [etherscanResult, setEtherscanResult] = useState(null); - const [etherscanError, setEtherscanError] = useState(null); - - // Similarity Verification - const [showSimilarityVerify, setShowSimilarityVerify] = useState(false); - const [simChainId, setSimChainId] = useState(1); - const [simAddress, setSimAddress] = useState(""); - const [simVerifying, setSimVerifying] = useState(false); - const [simResult, setSimResult] = useState(null); - const [simError, setSimError] = useState(null); - - // Standard JSON Verification - const verifyStandardJson = async () => { - setStdVerifying(true); - setStdError(null); - setStdResult(null); - - try { - if (!stdAddress || !stdContractName) { - throw new Error("Address and contract name are required"); - } - - const sources = JSON.parse(stdSources); - const settings = JSON.parse(stdSettings); - - const response = await fetch(`${SOURCIFY_API}/v2/verify/${stdChainId}/${stdAddress}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - contractName: stdContractName, - compilerVersion: "0.8.0", - sourceFiles: sources, - settings: settings, - }), - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || `HTTP ${response.status}: ${response.statusText}`); - } - - setStdResult(data); - - // Poll for verification status if we got a verification ID - if (data.verificationId) { - pollVerificationStatus(data.verificationId, setStdResult, setStdError); - } - } catch (err: any) { - setStdError(err.message || String(err)); - } finally { - setStdVerifying(false); - } - }; - - // Metadata Verification - const verifyMetadata = async () => { - setMetaVerifying(true); - setMetaError(null); - setMetaResult(null); - - try { - if (!metaAddress || !metadataJson) { - throw new Error("Address and metadata JSON are required"); - } - - const metadata = JSON.parse(metadataJson); - - const response = await fetch(`${SOURCIFY_API}/v2/verify/metadata/${metaChainId}/${metaAddress}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - metadata: metadata, - }), - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || `HTTP ${response.status}: ${response.statusText}`); - } - - setMetaResult(data); - - if (data.verificationId) { - pollVerificationStatus(data.verificationId, setMetaResult, setMetaError); - } - } catch (err: any) { - setMetaError(err.message || String(err)); - } finally { - setMetaVerifying(false); - } - }; - - // Etherscan Import - const importFromEtherscan = async () => { - setEtherscanImporting(true); - setEtherscanError(null); - setEtherscanResult(null); - - try { - if (!etherscanAddress) { - throw new Error("Address is required"); - } - - const url = new URL(`${SOURCIFY_API}/v2/verify/etherscan/${etherscanChainId}/${etherscanAddress}`); - if (etherscanApiKey) { - url.searchParams.append("apiKey", etherscanApiKey); - } - - const response = await fetch(url.toString(), { - method: "POST", - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || `HTTP ${response.status}: ${response.statusText}`); - } - - setEtherscanResult(data); - - if (data.verificationId) { - pollVerificationStatus(data.verificationId, setEtherscanResult, setEtherscanError); - } - } catch (err: any) { - setEtherscanError(err.message || String(err)); - } finally { - setEtherscanImporting(false); - } - }; - - // Similarity Verification - const verifySimilarity = async () => { - setSimVerifying(true); - setSimError(null); - setSimResult(null); - - try { - if (!simAddress) { - throw new Error("Address is required"); - } - - const response = await fetch(`${SOURCIFY_API}/v2/verify/similarity/${simChainId}/${simAddress}`, { - method: "POST", - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || `HTTP ${response.status}: ${response.statusText}`); - } - - setSimResult(data); - - if (data.verificationId) { - pollVerificationStatus(data.verificationId, setSimResult, setSimError); - } - } catch (err: any) { - setSimError(err.message || String(err)); - } finally { - setSimVerifying(false); - } - }; - - // Poll verification status - const pollVerificationStatus = async ( - verificationId: string, - setResult: (data: any) => void, - setError: (error: string) => void - ) => { - let attempts = 0; - const maxAttempts = 30; - const pollInterval = 2000; - - const poll = async () => { - try { - const response = await fetch(`${SOURCIFY_API}/v2/verify/${verificationId}`); - const data = await response.json(); - - if (data.status === "completed" || data.status === "failed") { - setResult(data); - return; - } - - attempts++; - if (attempts < maxAttempts) { - setTimeout(poll, pollInterval); - } else { - setError("Verification timed out. Check status manually."); - } - } catch (err: any) { - setError(err.message || String(err)); - } - }; - - poll(); - }; - - const renderResult = (result: any) => { - if (!result) return null; - return ( -
-
{JSON.stringify(result, null, 2)}
-
- ); - }; - - return ( -
- {/* Standard JSON Verification */} -
-
setShowStandardVerify(!showStandardVerify)} - > -

āœ… Verify Contract (Standard JSON)

- - {showStandardVerify ? "ā–¼" : "ā–¶"} - -
- {showStandardVerify && ( -
-
-
- - -
-
- - setStdAddress(e.target.value)} - /> -
-
- -
- - setStdContractName(e.target.value)} - /> -
- -
- -