diff --git a/.coverage-policy.json b/.coverage-policy.json index d2df2fe7e..9043a0f91 100644 --- a/.coverage-policy.json +++ b/.coverage-policy.json @@ -2,9 +2,56 @@ "version": 1, "typescript": { "ui": { - "minimumLines": 88.056, - "minimumFunctions": 80.397, - "allowedUnloadedFiles": ["ui/ts/app/App.tsx", "ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts", "ui/ts/features/universes/hooks/useZoltarOperations.ts", "ui/ts/index.ts", "ui/ts/simulation/tevmEngine.ts", "ui/ts/simulation/tevmWorker.ts"] + "minimumLines": 85.836, + "minimumFunctions": 75.376, + "allowedUnloadedFiles": [ + "ui/coreShared/ts/app/components/AppPageHeading.tsx", + "ui/coreShared/ts/app/components/GlobalTransactionTray.tsx", + "ui/coreShared/ts/components/AddressInfo.tsx", + "ui/coreShared/ts/components/ComparisonRecord.tsx", + "ui/coreShared/ts/components/LifecycleStageBanner.tsx", + "ui/coreShared/ts/components/LoadableValue.tsx", + "ui/coreShared/ts/components/OutcomeChipRow.tsx", + "ui/coreShared/ts/components/OutcomeSelectionList.tsx", + "ui/coreShared/ts/components/PaginationControls.tsx", + "ui/coreShared/ts/components/ProgressMeter.tsx", + "ui/coreShared/ts/components/Question.tsx", + "ui/coreShared/ts/components/RankedBarList.tsx", + "ui/coreShared/ts/components/RequirementsChecklist.tsx", + "ui/coreShared/ts/components/RouteHeader.tsx", + "ui/coreShared/ts/components/RouteWorkflowPanel.tsx", + "ui/coreShared/ts/components/ScalarOutcomePicker.tsx", + "ui/coreShared/ts/components/StickyObjectContext.tsx", + "ui/coreShared/ts/components/WorkflowSubsection.tsx", + "ui/coreShared/ts/copy/marketType.ts", + "ui/coreShared/ts/hooks/useErc20Loader.ts", + "ui/coreShared/ts/hooks/useFormState.ts", + "ui/coreShared/ts/lib/actionAvailability.ts", + "ui/coreShared/ts/lib/deploymentStatus.ts", + "ui/coreShared/ts/lib/formInputs.ts", + "ui/coreShared/ts/lib/marketType.ts", + "ui/coreShared/ts/lib/outcomeLabels.ts", + "ui/coreShared/ts/lib/questionId.ts", + "ui/coreShared/ts/lib/refreshState.ts", + "ui/coreShared/ts/lib/requireWalletConnection.ts", + "ui/coreShared/ts/lib/scalarOutcome.ts", + "ui/coreShared/ts/protocol/securityPoolActions.ts", + "ui/coreShared/ts/protocol/truthAuctionMath.ts", + "ui/coreShared/ts/simulation/tevmEngine.ts", + "ui/coreShared/ts/simulation/tevmWorker.ts", + "ui/statoblast/ts/app/App.tsx", + "ui/statoblast/ts/app/appPageTitle.ts", + "ui/statoblast/ts/app/components/AppRouteContent.tsx", + "ui/statoblast/ts/index.ts", + "ui/statoblast/ts/simulation/tevmWorker.ts", + "ui/zoltar/ts/app/App.tsx", + "ui/zoltar/ts/app/hooks/useAppRouteEffects.ts", + "ui/zoltar/ts/features/open-oracle/components/OpenOraclePriceValue.tsx", + "ui/zoltar/ts/features/universes/components/ShareMigrationTargetsSection.tsx", + "ui/zoltar/ts/features/universes/hooks/useZoltarOperations.ts", + "ui/zoltar/ts/index.ts", + "ui/zoltar/ts/simulation/tevmWorker.ts" + ] }, "shared": { "minimumLines": 76.923, @@ -16,11 +63,20 @@ "minimumFunctions": 25.476, "allowedUnloadedFiles": [ "scripts/check-agent-configs.mts", + "scripts/build-doc-charts.mts", + "scripts/build-docs-index.mts", + "scripts/check-doc-charts.mts", "scripts/check-docs-content.mts", "scripts/check-docs-examples.mts", "scripts/check-docs-html.mts", "scripts/check-docs-reference-values.mts", - "scripts/check-mainnet-deployment.mts", + "scripts/check-html-format.mts", + "scripts/check-uniswap-deployment-artifact.mts", + "scripts/check-unit-terminology.mts", + "scripts/contract-reference-metadata.mts", + "scripts/docs-html-page.mts", + "scripts/docs-html-text.mts", + "scripts/documentationChartBuild.mts", "scripts/generate-contract-interaction-reference.mts", "scripts/lint-no-bare-catch.mts", "scripts/lint-no-direct-viem-imports.mts", @@ -31,13 +87,14 @@ "scripts/run-tests.mts", "scripts/run-typescript-coverage.mts", "solidity/ts/gas-costs.ts", - "ui/build/production.mts", - "ui/build/tests.mts", - "ui/build/watch.mts", - "ui/build/workers.mts", - "ui/dev-server.ts", - "ui/ts/index.dev.ts", - "ui/ts/liveReload.ts" + "ui/coreShared/build/production.mts", + "ui/coreShared/build/watch.mts", + "ui/coreShared/build/workers.mts", + "ui/coreShared/dev-server.ts", + "ui/statoblast/ts/index.dev.ts", + "ui/statoblast/ts/liveReload.ts", + "ui/zoltar/ts/index.dev.ts", + "ui/zoltar/ts/liveReload.ts" ] } }, @@ -45,6 +102,6 @@ "minimumFirstPartyLines": 99.9 }, "changedLines": { - "minimum": 90 + "minimum": 85.836 } } diff --git a/.dockerignore b/.dockerignore index 001350f6b..407778a87 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,14 +20,14 @@ solidity/ts/**/*.d.ts.map solidity/ts/**/*.js solidity/ts/**/*.js.map solidity/ts/types/contractArtifact.ts -ui/dist -ui/js -ui/vendor -ui/ts/**/*.d.ts -ui/ts/**/*.d.ts.map -ui/ts/**/*.js -ui/ts/**/*.js.map -ui/ts/abis.ts -ui/ts/contractArtifact.ts -ui/ts/deploymentArtifacts.ts -ui/ts/deploymentsArtifacts.ts +ui/*/dist +ui/*/js +ui/*/vendor +ui/*/ts/**/*.d.ts +ui/*/ts/**/*.d.ts.map +ui/*/ts/**/*.js +ui/*/ts/**/*.js.map +ui/*/ts/abis.ts +ui/*/ts/contractArtifact.ts +ui/*/ts/deploymentArtifacts.ts +ui/*/ts/deploymentsArtifacts.ts diff --git a/.github/actions/setup-ci/action.yml b/.github/actions/setup-ci/action.yml index 6c32afccd..1ee2bf7d5 100644 --- a/.github/actions/setup-ci/action.yml +++ b/.github/actions/setup-ci/action.yml @@ -34,13 +34,16 @@ runs: uses: actions/cache@v5 with: path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock', 'ui/bun.lock', 'solidity/bun.lock') }} + key: ${{ runner.os }}-bun-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock', 'ui/*/bun.lock', 'solidity/bun.lock') }} - name: Install dependencies shell: bash run: | bun install --frozen-lockfile - (cd ui && bun install --frozen-lockfile) + (cd ui/coreShared && bun install --frozen-lockfile) + (cd ui/zoltar && bun install --frozen-lockfile) + (cd ui/statoblast && bun install --frozen-lockfile) + (cd ui/trading && bun install --frozen-lockfile) (cd solidity && bun install --frozen-lockfile) - name: Cache generated shared package @@ -59,16 +62,20 @@ runs: solidity/.contract-hash.json solidity/ts/types/contractArtifact.ts solidity/types/contractArtifact.ts - ui/ts/abis.ts - ui/ts/contractArtifact.ts - key: ${{ runner.os }}-contracts-${{ env.BUN_VERSION }}-${{ env.FOUNDRY_VERSION }}-${{ hashFiles('bun.lock', 'solidity/bun.lock', 'solidity/package.json', 'solidity/tsconfig-compile.json', 'solidity/ts/abi/abis.ts', 'solidity/ts/compile.ts', 'solidity/contracts/**', 'ui/build/projectArtifacts.mts') }} + ui/coreShared/ts/abis.ts + ui/coreShared/ts/contractArtifact.ts + ui/trading/ts/generated/contractArtifact.ts + key: ${{ runner.os }}-contracts-${{ env.BUN_VERSION }}-${{ env.FOUNDRY_VERSION }}-${{ hashFiles('bun.lock', 'solidity/bun.lock', 'solidity/package.json', 'solidity/tsconfig-compile.json', 'solidity/ts/abi/abis.ts', 'solidity/ts/compile.ts', 'solidity/contracts/**', 'ui/coreShared/build/projectArtifacts.mts') }} - name: Cache UI vendor assets id: vendor-cache uses: actions/cache@v5 with: - path: ui/vendor - key: ${{ runner.os }}-ui-vendor-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock', 'ui/bun.lock', 'ui/package.json', 'ui/tsconfig.vendor.json', 'ui/build/bundlerPaths.mts', 'ui/build/vendor.mts') }} + path: | + ui/zoltar/vendor + ui/statoblast/vendor + ui/trading/vendor + key: ${{ runner.os }}-ui-vendor-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock', 'ui/*/bun.lock', 'ui/*/package.json', 'ui/coreShared/build/bundlerPaths.mts', 'ui/coreShared/build/vendor.mts') }} - name: Generate and verify generated artifacts if: inputs.verify-generated == 'true' || steps.shared-cache.outputs.cache-hit != 'true' || steps.contract-cache.outputs.cache-hit != 'true' || steps.vendor-cache.outputs.cache-hit != 'true' diff --git a/.github/actions/setup-component/action.yml b/.github/actions/setup-component/action.yml index 58c6301b6..b6e2bd57b 100644 --- a/.github/actions/setup-component/action.yml +++ b/.github/actions/setup-component/action.yml @@ -41,5 +41,8 @@ runs: if: inputs.artifacts == 'true' shell: bash run: | - (cd ui && bun install --frozen-lockfile) + (cd ui/coreShared && bun install --frozen-lockfile) + (cd ui/zoltar && bun install --frozen-lockfile) + (cd ui/statoblast && bun install --frozen-lockfile) + (cd ui/trading && bun install --frozen-lockfile) (cd solidity && bun install --frozen-lockfile) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48b178aba..5977684f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,8 +85,9 @@ jobs: shared/js solidity/artifacts solidity/ts/types/contractArtifact.ts - ui/ts/abis.ts - ui/ts/contractArtifact.ts + ui/coreShared/ts/abis.ts + ui/coreShared/ts/contractArtifact.ts + ui/trading/ts/generated/contractArtifact.ts include-hidden-files: true if-no-files-found: error retention-days: 1 @@ -110,7 +111,7 @@ jobs: uses: actions/cache@v5 with: path: .tsbuildinfo - key: ${{ runner.os }}-tsbuildinfo-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock', 'package.json', 'tsconfig.json') }}-${{ hashFiles('shared/ts/**', 'ui/ts/**') }} + key: ${{ runner.os }}-tsbuildinfo-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock', 'package.json', 'tsconfig.json') }}-${{ hashFiles('shared/ts/**', 'ui/*/ts/**') }} restore-keys: | ${{ runner.os }}-tsbuildinfo-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock', 'package.json', 'tsconfig.json') }}- - name: Verify pre-task worktree cleanliness @@ -119,12 +120,33 @@ jobs: test -z "$(git status --porcelain --untracked-files=normal)" git diff --check - name: TypeScript checks and production UI build - run: bun run ci:preflight:current + shell: bash + run: | + started_at="${SECONDS}" + set +e + bun run ui:build:apps + build_status="$?" + if [[ "${build_status}" -ne 0 ]]; then + exit "${build_status}" + fi + bun run ci:preflight:current + status="$?" + set -e + duration="$((SECONDS - started_at))" + echo "| Preflight and production build | ${duration}s |" >> "${GITHUB_STEP_SUMMARY}" + exit "${status}" - name: Upload production UI build uses: actions/upload-artifact@v4 with: name: production-ui - path: ui/dist + path: | + ui/coreShared/js + ui/zoltar/js + ui/statoblast/js + ui/trading/js + ui/zoltar/dist + ui/statoblast/dist + ui/trading/dist if-no-files-found: error retention-days: 1 - name: Upload component build inputs @@ -135,8 +157,9 @@ jobs: shared/js solidity/artifacts solidity/ts/types/contractArtifact.ts - ui/ts/abis.ts - ui/ts/contractArtifact.ts + ui/coreShared/ts/abis.ts + ui/coreShared/ts/contractArtifact.ts + ui/trading/ts/generated/contractArtifact.ts include-hidden-files: true if-no-files-found: error retention-days: 1 @@ -163,8 +186,16 @@ jobs: - uses: actions/download-artifact@v5 with: name: production-ui - path: ui/dist - - uses: actions/cache/restore@v5 + path: ui + - name: Refresh split UI package installs + run: | + bun ./scripts/install-frozen.mts ui/coreShared + bun ./scripts/install-frozen.mts ui/zoltar + bun ./scripts/install-frozen.mts ui/statoblast + bun ./scripts/install-frozen.mts ui/trading + + - name: Restore historical test timings + uses: actions/cache/restore@v5 with: path: .ci/test-timings.json key: ${{ runner.os }}-test-timings-v1-${{ github.sha }} @@ -244,7 +275,8 @@ jobs: - uses: oven-sh/setup-bun@v2 with: bun-version: ${{ env.BUN_VERSION }} - - run: bun audit && (cd ui && bun audit) && (cd solidity && bun audit) + - name: Run dependency audits + run: bun audit && (cd ui/coreShared && bun audit) && (cd ui/zoltar && bun audit) && (cd ui/statoblast && bun audit) && (cd ui/trading && bun audit) && (cd solidity && bun audit) infrastructure-checks: name: Infrastructure checks diff --git a/.github/workflows/deploy-testnet.yml b/.github/workflows/deploy-testnet.yml index b1dc9cfeb..c753a5e69 100644 --- a/.github/workflows/deploy-testnet.yml +++ b/.github/workflows/deploy-testnet.yml @@ -60,11 +60,19 @@ jobs: run: | bun install --frozen-lockfile (cd solidity && bun install --frozen-lockfile) - (cd ui && bun install --frozen-lockfile) + (cd ui/coreShared && bun install --frozen-lockfile) + (cd ui/zoltar && bun install --frozen-lockfile) + (cd ui/statoblast && bun install --frozen-lockfile) + (cd ui/trading && bun install --frozen-lockfile) - name: Prepare contract artifacts run: bun run ensure-contract-artifacts + - name: Build UI deployment dependencies + run: | + bun run ui:build:apps + bun ./scripts/deploy-testnet.mts --help > /dev/null + - name: Deploy deterministic infrastructure env: CHAIN_ID: ${{ inputs.chain_id }} diff --git a/.github/workflows/version-deploy.yml b/.github/workflows/version-deploy.yml index 23b05b188..51f33b7f6 100644 --- a/.github/workflows/version-deploy.yml +++ b/.github/workflows/version-deploy.yml @@ -50,8 +50,7 @@ jobs: shell: bash run: | IMAGE_ROOT="${REGISTRY}/$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')" - echo "ZOLTAR_IMAGE=${IMAGE_ROOT}:${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" - echo "TRADING_IMAGE=${IMAGE_ROOT}-trading:${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" + echo "PUBLISHER_IMAGE=${IMAGE_ROOT}:${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -63,7 +62,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build Zoltar IPFS publisher + - name: Build IPFS publisher uses: docker/build-push-action@v6 with: build-args: BUN_VERSION=${{ env.BUN_VERSION }} @@ -72,34 +71,18 @@ jobs: context: . file: ui/Dockerfile load: true - tags: ${{ env.ZOLTAR_IMAGE }} - - - name: Build trading IPFS publisher - uses: docker/build-push-action@v6 - with: - build-args: BUN_VERSION=${{ env.BUN_VERSION }} - cache-from: type=gha,scope=trading-version-ipfs - cache-to: type=gha,mode=max,scope=trading-version-ipfs - context: . - file: trading/Dockerfile - load: true target: publisher - tags: ${{ env.TRADING_IMAGE }} + tags: ${{ env.PUBLISHER_IMAGE }} - - name: Read content identifiers + - name: Read content identifier shell: bash run: | - ZOLTAR_CID=$(docker run --rm --entrypoint /bin/sh "${ZOLTAR_IMAGE}" -c 'cat /ipfs_hash.txt') - TRADING_CID=$(docker run --rm --entrypoint /bin/sh "${TRADING_IMAGE}" -c 'cat /ipfs_hash.txt') - [[ "${ZOLTAR_CID}" =~ ^bafy[a-z2-7]+$ ]] - [[ "${TRADING_CID}" =~ ^bafy[a-z2-7]+$ ]] - echo "ZOLTAR_CID=${ZOLTAR_CID}" >> "$GITHUB_ENV" - echo "TRADING_CID=${TRADING_CID}" >> "$GITHUB_ENV" - - - name: Push versioned publisher images - run: | - docker push "${ZOLTAR_IMAGE}" - docker push "${TRADING_IMAGE}" + IPFS_CID=$(docker run --rm --entrypoint /bin/sh "${PUBLISHER_IMAGE}" -c 'cat /ipfs_hash.txt') + [[ "${IPFS_CID}" =~ ^bafy[a-z2-7]+$ ]] + echo "IPFS_CID=${IPFS_CID}" >> "$GITHUB_ENV" + + - name: Push versioned publisher image + run: docker push "${PUBLISHER_IMAGE}" - name: Create or update GitHub release env: @@ -113,23 +96,16 @@ jobs: cat > release-notes.md </ts/**/*.ts(x)` sources outside its tests and do not touch contracts, `ui/coreShared/build`, `ui/coreShared/dev-server.ts`, generated artifacts, or consumers of refreshed contract output, run: ```bash -cd ui && bun x tsc --project tsconfig.json +cd ui/zoltar && bun x tsc --project tsconfig.json ``` -Use full `bun run tsc` for UI tests, UI build scripts, `ui/dev-server.ts`, `ui/tsconfig.json`, package scripts, mixed UI/non-UI TypeScript, contracts, or generated contract output. +(or the matching `ui/statoblast`, `ui/trading`, or `ui/coreShared` project). Use full `bun run tsc` for UI tests, UI build scripts, `ui/coreShared/dev-server.ts`, any UI tsconfig, package scripts, mixed UI/non-UI TypeScript, contracts, or generated contract output. Skip TypeScript for prose-only, instruction-only, `.codex/agents`-only, formatting-only, or comment-only changes that cannot affect generated imports or executable behavior. @@ -118,9 +119,10 @@ pins the deployment input and prevents its large upstream packages from entering | `shared/js/**` | `bun run shared:build` | | `solidity/artifacts/Contracts.json` | `bun run compile-contracts` | | `solidity/ts/types/contractArtifact.ts` | `bun run compile-contracts` | -| `ui/ts/contractArtifact.ts` | `bun run generate` or `bun run ui:build` | -| `ui/js/**` | UI TypeScript build | -| `ui/vendor/**` | `bun run ui:vendor` | +| `ui/coreShared/ts/contractArtifact.ts` and `ui/coreShared/ts/abis.ts` | `bun run generate` or `bun run ui:build` | +| `ui/*/js/**` | UI TypeScript builds per package | +| `ui/trading/ts/generated/contractArtifact.ts` | `bun run compile-contracts`, `bun run generate`, or `bun run trading:ui:build` | +| `ui/*/vendor/**` | `bun run ui:vendor` | | `docs/assets/js/chartRuntime.js` | `bun run docs:build-charts` | | `docs/assets/js/docsShell.js` | `bun run docs:build-runtime` | | `docs/assets/js/responsiveDocs.js` | `bun run docs:build-runtime` | @@ -137,7 +139,7 @@ Do not regenerate or commit these outputs unless the task requires them or a req ### 7. UI manual QA -For visual, responsive, routing, form, or transaction-state behavior, perform browser QA in addition to automated checks. Use `bun run ui:serve` and `http://localhost:12345/?simulate=1` for walletless testing. +For visual, responsive, routing, form, or transaction-state behavior, perform browser QA in addition to automated checks. Use `bun run app:serve:zoltar` (`http://localhost:12346/?simulate=1`), `bun run app:serve:statoblast` (`http://localhost:12347/?simulate=1`), or `bun run app:serve:trading` (`http://localhost:4163/?simulate=1`) for walletless testing. Choose the smallest relevant scenario: @@ -145,6 +147,7 @@ Choose the smallest relevant scenario: - `simScenario=deployed` - `simScenario=security-pool` - `simScenario=securitypoolx2` +- `simScenario=trading` Check the changed flow at desktop and narrow/mobile widths, including relevant empty, loading, disabled, pending, success, and failure states. Uniswap-backed REP pricing is intentionally unavailable in simulation; quote-dependent UI must degrade gracefully. diff --git a/README.md b/README.md index a35d88f62..0072549cb 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,17 @@ This repository contains two protocol layers: The codebase is split into these main areas: - `solidity/` contains contracts, protocol test support, tests, and generated contract artifacts -- `ui/` contains the Preact frontend, organized by application shell, feature, and protocol-client boundaries +- `ui/coreShared/` contains the shared Preact primitives, application-shell framework, simulation engine, and per-app build tooling used by all three interfaces +- `ui/zoltar/` contains the Zoltar oracle operations interface (its own package, dev server, and production build) +- `ui/statoblast/` contains the Augur Statoblast prediction-market operations interface (its own package, dev server, and production build) +- `ui/trading/` contains the Statoblast Trading interface (its own package, dev server, and production build) +- `solidity/contracts/trading/` contains the Trading contracts, `shared/ts/trading/` contains reusable AMM math, and contract-facing tooling and tests live under `solidity/ts` - `shared/` contains runtime-neutral TypeScript used by Solidity tooling and the UI - `docs/` contains the published protocol documentation - `scripts/` contains repository-wide build, validation, and test orchestration - `bots/` contains liquidator and open oracle arbitrager bots -Inside `ui/ts`, route-specific code belongs under `features/`, cross-feature UI primitives remain in `components`, application composition belongs in `app`, and contract reads and writes belong in `protocol`. +Each interface package (`ui/zoltar`, `ui/statoblast`, `ui/trading`) keeps route-specific code under `ts/features`, application composition in `ts/app`, and contract reads and writes in `ts/protocol`. Cross-feature primitives, hooks, lib helpers, the simulation engine, and the app-shell framework live in `ui/coreShared/ts`. Imports point inward along `coreShared ← Zoltar ← Statoblast ← Trading`; shared packages never import an application that consumes them. Protocol documentation lives in [docs/documentation.html](https://augurproject.github.io/zoltar/docs/documentation.html) @@ -49,12 +53,14 @@ Important: After completing [Setup](#setup), start a local chain and launch the app: 1. Start the repository-pinned local chain with `bun run anvil` -1. Run `bun run app:serve` +1. Run `bun run app:serve:zoltar` for Zoltar (http://localhost:12346), `bun run app:serve:statoblast` for Statoblast (http://localhost:12347), or `bun run app:serve:trading` for Trading (http://localhost:4163) If you are iterating on the app and want rebuilds, use: ```bash -bun run app:watch +bun run app:watch:zoltar +bun run app:watch:statoblast +bun run app:watch:trading ``` ## RPC Configuration @@ -197,34 +203,38 @@ those features are used. The UI also supports a walletless browser-local simulation mode for manual QA. After completing [Setup](#setup): -1. Run `bun run app:serve` -1. Open `http://localhost:12345/?simulate=1` +1. Run `bun run app:serve:zoltar` (Zoltar on port 12346), `bun run app:serve:statoblast` (Statoblast on port 12347), or `bun run app:serve:trading` (Trading on port 4163) +1. Open `http://localhost:12346/?simulate=1`, `http://localhost:12347/?simulate=1`, or `http://localhost:4163/?simulate=1` -This mode does not require a wallet extension or `anvil`. Instead, it boots a Tevm-backed in-browser chain, seeds the QA accounts with ETH, WETH, and REP, and leaves the application contracts undeployed so the UI starts on the deploy flow. +This mode does not require a wallet extension or `anvil`. Instead, it boots a Tevm-backed in-browser chain and seeds the QA accounts with ETH, WETH, and REP. Zoltar and Statoblast scenarios control whether application contracts are already deployed. Trading defaults to `simScenario=trading`, which deploys a seeded SecurityPool plus the Trading factory and router so its market routes are immediately usable. Simulation mode details: - The activation flag is `?simulate=1` - The flag is intentionally not restricted to localhost or development builds; production deployments may expose it as a browser-local demo and manual-QA path - Production users should treat any `?simulate=1` URL as a local sandbox. Simulated balances, deployments, blocks, quotes, and transactions are local to the browser and are not evidence of mainnet state. -- Supported seeded scenarios are `simScenario=baseline`, `simScenario=deployed`, `simScenario=security-pool`, `simScenario=securitypoolx2`, and `simScenario=securitypoolx2-auction` +- Supported seeded scenarios are `simScenario=baseline`, `simScenario=deployed`, `simScenario=security-pool`, `simScenario=securitypoolx2`, `simScenario=securitypoolx2-auction`, and `simScenario=trading` - The live simulation chain is ephemeral and exists only in the current brow ## Common Commands -Run the full app in development mode. This includes contract generation and the frontend build pipeline: +Run each interface in development mode. Each command first builds the complete dependency-ordered UI workspace, then serves the selected app: ```bash -bun run app:serve +bun run app:serve:zoltar # Zoltar on http://localhost:12346 +bun run app:serve:statoblast # Statoblast on http://localhost:12347 +bun run app:serve:trading # Trading on http://localhost:4163 ``` -Watch and rebuild the full app pipeline: +Watch the selected app and its dependencies after the complete workspace build: ```bash -bun run app:watch +bun run app:watch:zoltar +bun run app:watch:statoblast +bun run app:watch:trading ``` -Build the full app: +Build all UI apps: ```bash bun run app:build diff --git a/augurScan/bun.lock b/augurScan/bun.lock index 8988e5a74..8df0ad13c 100644 --- a/augurScan/bun.lock +++ b/augurScan/bun.lock @@ -11,7 +11,6 @@ "devDependencies": { "@biomejs/biome": "2.3.11", "@types/bun": "1.3.11", - "solc": "0.8.35", "typescript": "5.8.2", }, }, @@ -47,28 +46,10 @@ "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], - "command-exists": ["command-exists@1.2.9", "", {}, "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="], - - "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - - "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], - - "js-sha3": ["js-sha3@0.8.0", "", {}, "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="], - - "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], - "micro-eth-signer": ["micro-eth-signer@0.18.1", "", { "dependencies": { "@noble/curves": "^2.0.0", "@noble/hashes": "^2.0.0", "micro-packed": "^0.8.0" } }, "sha512-vXKhCZxrytpl+dXR9JeaE41ZVFndi7wCKc1Jd22smOMAeDErvRcXaTxhYUf1yQxis4n2kIdv/pH7iuf+5/cj+Q=="], "micro-packed": ["micro-packed@0.8.0", "", { "dependencies": { "@scure/base": "2.0.0" } }, "sha512-AKb8znIvg9sooythbXzyFeChEY0SkW0C6iXECpy/ls0e5BtwXO45J9wD9SLzBztnS4XmF/5kwZknsq+jyynd/A=="], - "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], - - "semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - - "solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], - - "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="], - "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], diff --git a/augurScan/config/abis.json b/augurScan/config/abis.json index 061350c32..79fcba583 100644 --- a/augurScan/config/abis.json +++ b/augurScan/config/abis.json @@ -1,8 +1,50 @@ { - "sourceHash": "8aa2443d4c12da296f8fb3b92057b00ea37591ab849b4baee3fe8cbd6b3fb202", + "sourceHash": "504f6c2fdd35b7ff5e81938e1b9a21ec380ae6813a6f4662d8dc3583f2e83d85", "contracts": { - "IERC1363": { - "source": "contracts/statoblast/openOracle/openzeppelin/contracts/interfaces/IERC1363.sol", + "DeploymentStatusOracle": { + "source": "contracts/DeploymentStatusOracle.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address[]", + "name": "_deploymentAddresses", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address[]", + "name": "deploymentAddresses", + "type": "address[]" + } + ], + "name": "DeploymentAddressesSet", + "type": "event" + }, + { + "inputs": [], + "name": "getDeploymentMask", + "outputs": [ + { + "internalType": "uint256", + "name": "deployedMask", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + }, + "ERC20": { + "source": "contracts/ERC20.sol", "abi": [ { "anonymous": false, @@ -106,88 +148,55 @@ "inputs": [ { "internalType": "address", - "name": "spender", + "name": "account", "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" } ], - "name": "approveAndCall", + "name": "balanceOf", "outputs": [ { - "internalType": "bool", + "internalType": "uint256", "name": "", - "type": "bool" + "type": "uint256" } ], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "approveAndCall", + "inputs": [], + "name": "decimals", "outputs": [ { - "internalType": "bool", + "internalType": "uint8", "name": "", - "type": "bool" + "type": "uint8" } ], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", + "inputs": [], + "name": "name", "outputs": [ { - "internalType": "uint256", + "internalType": "string", "name": "", - "type": "uint256" + "type": "string" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", + "inputs": [], + "name": "symbol", "outputs": [ { - "internalType": "bool", + "internalType": "string", "name": "", - "type": "bool" + "type": "string" } ], "stateMutability": "view", @@ -232,6 +241,11 @@ }, { "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, { "internalType": "address", "name": "to", @@ -243,7 +257,7 @@ "type": "uint256" } ], - "name": "transferAndCall", + "name": "transferFrom", "outputs": [ { "internalType": "bool", @@ -253,46 +267,107 @@ ], "stateMutability": "nonpayable", "type": "function" + } + ] + }, + "GenesisReputationToken": { + "source": "contracts/GenesisReputationToken.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address[]", + "name": "initialHolders", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "initialBalances", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" }, { + "anonymous": false, "inputs": [ { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { + "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" }, { - "internalType": "bytes", - "name": "data", - "type": "bytes" + "internalType": "address", + "name": "spender", + "type": "address" } ], - "name": "transferAndCall", + "name": "allowance", "outputs": [ { - "internalType": "bool", + "internalType": "uint256", "name": "", - "type": "bool" + "type": "uint256" } ], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", + "name": "spender", "type": "address" }, { @@ -301,7 +376,7 @@ "type": "uint256" } ], - "name": "transferFrom", + "name": "approve", "outputs": [ { "internalType": "bool", @@ -316,9 +391,88 @@ "inputs": [ { "internalType": "address", - "name": "from", + "name": "account", "type": "address" - }, + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalTheoreticalSupplyAttoRep", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "to", @@ -328,14 +482,9 @@ "internalType": "uint256", "name": "value", "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" } ], - "name": "transferFromAndCall", + "name": "transfer", "outputs": [ { "internalType": "bool", @@ -364,7 +513,7 @@ "type": "uint256" } ], - "name": "transferFromAndCall", + "name": "transferFrom", "outputs": [ { "internalType": "bool", @@ -378,7 +527,7 @@ ] }, "IERC20": { - "source": "contracts/statoblast/openOracle/openzeppelin/contracts/token/ERC20/IERC20.sol", + "source": "contracts/IERC20.sol", "abi": [ { "anonymous": false, @@ -565,663 +714,8 @@ } ] }, - "SafeERC20": { - "source": "contracts/statoblast/openOracle/openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "currentAllowance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "requestedDecrease", - "type": "uint256" - } - ], - "name": "SafeERC20FailedDecreaseAllowance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "SafeERC20FailedOperation", - "type": "error" - } - ] - }, - "ReentrancyGuard": { - "source": "contracts/statoblast/openOracle/openzeppelin/contracts/utils/ReentrancyGuard.sol", - "abi": [ - { - "inputs": [], - "name": "ReentrancyGuardReentrantCall", - "type": "error" - } - ] - }, - "IERC165": { - "source": "contracts/statoblast/openOracle/openzeppelin/contracts/utils/introspection/IERC165.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } - ] - }, - "SafeCast": { - "source": "contracts/statoblast/openOracle/openzeppelin/contracts/utils/math/SafeCast.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "uint8", - "name": "bits", - "type": "uint8" - }, - { - "internalType": "int256", - "name": "value", - "type": "int256" - } - ], - "name": "SafeCastOverflowedIntDowncast", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "int256", - "name": "value", - "type": "int256" - } - ], - "name": "SafeCastOverflowedIntToUint", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "bits", - "type": "uint8" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "SafeCastOverflowedUintDowncast", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "SafeCastOverflowedUintToInt", - "type": "error" - } - ] - }, - "DeploymentStatusOracle": { - "source": "contracts/DeploymentStatusOracle.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address[]", - "name": "_deploymentAddresses", - "type": "address[]" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address[]", - "name": "deploymentAddresses", - "type": "address[]" - } - ], - "name": "DeploymentAddressesSet", - "type": "event" - }, - { - "inputs": [], - "name": "getDeploymentMask", - "outputs": [ - { - "internalType": "uint256", - "name": "deployedMask", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } - ] - }, - "ERC20": { - "source": "contracts/ERC20.sol", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ] - }, - "GenesisReputationToken": { - "source": "contracts/GenesisReputationToken.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address[]", - "name": "initialHolders", - "type": "address[]" - }, - { - "internalType": "uint256[]", - "name": "initialBalances", - "type": "uint256[]" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTotalTheoreticalSupplyAttoRep", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ] - }, - "IERC20Metadata": { - "source": "contracts/IERC20Metadata.sol", + "IERC20Metadata": { + "source": "contracts/IERC20Metadata.sol", "abi": [ { "anonymous": false, @@ -2544,6 +2038,7 @@ "type": "uint256" }, { + "indexed": false, "components": [ { "internalType": "string", @@ -2586,7 +2081,6 @@ "type": "string" } ], - "indexed": false, "internalType": "struct ZoltarQuestionData.QuestionData", "name": "questionData", "type": "tuple" @@ -30628,6 +30122,30 @@ } ] }, + "IERC165": { + "source": "contracts/statoblast/interfaces/IERC165.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + }, "IEscalationGameEvents": { "source": "contracts/statoblast/interfaces/IEscalationGame.sol", "abi": [ @@ -39091,8 +38609,110 @@ } ] }, + "PredeploymentShareSink": { + "source": "contracts/trading/PredeploymentShareSink.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + } + ] + }, "TwoWayConstantProductFactory": { - "source": "trading/contracts/TwoWayConstantProductFactory.sol", + "source": "contracts/trading/TwoWayConstantProductFactory.sol", "abi": [ { "inputs": [ @@ -39265,7 +38885,7 @@ ] }, "TwoWayConstantProductPair": { - "source": "trading/contracts/TwoWayConstantProductPair.sol", + "source": "contracts/trading/TwoWayConstantProductPair.sol", "abi": [ { "inputs": [ @@ -40268,6 +39888,1926 @@ "type": "function" } ] + }, + "TwoWayConstantProductRouter": { + "source": "contracts/trading/TwoWayConstantProductRouter.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "contract ITwoWayConstantProductFactory", + "name": "_factory", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "uint256", + "name": "minLiquidity", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "addLiquidityWithEth", + "outputs": [ + { + "components": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "uint256", + "name": "completeSetShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "yesUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "yesReturned", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noReturned", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "invalidInsurance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "internalType": "struct TwoWayConstantProductRouter.LiquidityResult", + "name": "result", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ISecurityPool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "conditionalYesBpsValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minLiquidity", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "createPairAndInitializeWithEth", + "outputs": [ + { + "components": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "uint256", + "name": "completeSetShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "yesUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "yesReturned", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noReturned", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "invalidInsurance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "internalType": "struct TwoWayConstantProductRouter.LiquidityResult", + "name": "result", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "enum BinaryOutcomes.BinaryOutcome", + "name": "longOutcome", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "minLongSharesOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "enterPosition", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "ethSpent", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "completeSetShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "oppositeSharesSwapped", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "additionalLongShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalLongShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "invalidInsurance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "conditionalYesBpsBefore", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "conditionalYesBpsAfter", + "type": "uint256" + } + ], + "internalType": "struct TwoWayConstantProductRouter.EnterResult", + "name": "result", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "enum BinaryOutcomes.BinaryOutcome", + "name": "longOutcome", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "completeSetSharesToRedeem", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLongSharesIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minEthOut", + "type": "uint256" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "exitPosition", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "completeSetShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "longSharesSwapped", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalLongShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "invalidInsurance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ethOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "internalType": "struct TwoWayConstantProductRouter.ExitResult", + "name": "result", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "contract ITwoWayConstantProductFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "uint256", + "name": "conditionalYesBpsValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minLiquidity", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "initializeWithEth", + "outputs": [ + { + "components": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "uint256", + "name": "completeSetShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "yesUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "yesReturned", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noReturned", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "invalidInsurance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "internalType": "struct TwoWayConstantProductRouter.LiquidityResult", + "name": "result", + "type": "tuple" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ISecurityPool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "completeSetSharesToRedeem", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minEthOut", + "type": "uint256" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "redeemCompleteSet", + "outputs": [ + { + "internalType": "uint256", + "name": "ethOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minYesOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNoOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "removeLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "yesOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] + }, + "ITradingShareToken": { + "source": "contracts/trading/interfaces/ITradingShareToken.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "actor", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "authorized", + "type": "bool" + } + ], + "name": "AuthorizationUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "contract ISecurityPool", + "name": "_securityPoolCandidate", + "type": "address" + } + ], + "name": "authorize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "owners", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "balances_", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + }, + { + "internalType": "enum BinaryOutcomes.BinaryOutcome", + "name": "_outcome", + "type": "uint8" + }, + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "balanceOfOutcome", + "outputs": [ + { + "internalType": "uint256", + "name": "balanceAttoShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + }, + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "balanceOfShares", + "outputs": [ + { + "internalType": "uint256[3]", + "name": "balancesAttoShares", + "type": "uint256[3]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountAttoShares", + "type": "uint256" + } + ], + "name": "burnCompleteSets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "burnTokenIdAndGetRemainingSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "balanceAttoShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "remainingSupplyAttoShares", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "universeId", + "type": "uint248" + } + ], + "name": "canonicalPoolByUniverse", + "outputs": [ + { + "internalType": "contract ISecurityPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromId", + "type": "uint256" + }, + { + "internalType": "uint248", + "name": "targetUniverseId", + "type": "uint248" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getMigratedShareAmountAttoShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + }, + { + "internalType": "enum BinaryOutcomes.BinaryOutcome", + "name": "_outcome", + "type": "uint8" + } + ], + "name": "getTokenId", + "outputs": [ + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + }, + { + "internalType": "enum BinaryOutcomes.BinaryOutcome[]", + "name": "_outcomes", + "type": "uint8[]" + } + ], + "name": "getTokenIds", + "outputs": [ + { + "internalType": "uint256[]", + "name": "_tokenIds", + "type": "uint256[]" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isAuthorized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + } + ], + "name": "maximumOutcomeSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "maximumSupplyAttoShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "targetOutcomeIndexes", + "type": "uint256[]" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + }, + { + "internalType": "address", + "name": "_account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountAttoShares", + "type": "uint256" + } + ], + "name": "mintCompleteSets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint248", + "name": "_universeId", + "type": "uint248" + }, + { + "internalType": "enum BinaryOutcomes.BinaryOutcome", + "name": "_outcome", + "type": "uint8" + } + ], + "name": "totalSupplyForOutcome", + "outputs": [ + { + "internalType": "uint256", + "name": "totalSupplyAttoShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "unpackTokenId", + "outputs": [ + { + "internalType": "uint248", + "name": "_universe", + "type": "uint248" + }, + { + "internalType": "enum BinaryOutcomes.BinaryOutcome", + "name": "_outcome", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + } + ] + }, + "ITwoWayConstantProductFactory": { + "source": "contracts/trading/interfaces/ITwoWayConstantProductFactory.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "contract ISecurityPool", + "name": "pool", + "type": "address" + } + ], + "name": "createPair", + "outputs": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeBps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ISecurityPool", + "name": "pool", + "type": "address" + } + ], + "name": "getPair", + "outputs": [ + { + "internalType": "contract ITwoWayConstantProductPair", + "name": "pair", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "candidate", + "type": "address" + } + ], + "name": "isPair", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "predeploymentShareSink", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ISecurityPool", + "name": "pool", + "type": "address" + } + ], + "name": "predictPair", + "outputs": [ + { + "internalType": "address", + "name": "predicted", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "securityPoolFactory", + "outputs": [ + { + "internalType": "contract ISecurityPoolFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + }, + "ITwoWayConstantProductPair": { + "source": "contracts/trading/interfaces/ITwoWayConstantProductPair.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxYes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minLiquidity", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "addLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "yesUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noUsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeBps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getEffectiveReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "yesReserve", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noReserve", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "yesReserve", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noReserve", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "yesAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minLiquidity", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "initialize", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "invalidTokenId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "noTokenId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "yesForNo", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "name": "quoteExactInput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "yesForNo", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "quoteExactOutput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minYes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNo", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "removeLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "yesOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "noOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "securityPool", + "outputs": [ + { + "internalType": "contract ISecurityPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "shareToken", + "outputs": [ + { + "internalType": "contract ITradingShareToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "yesForNo", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "swapExactInput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "yesForNo", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "swapExactOutput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tradingStatus", + "outputs": [ + { + "internalType": "enum ITwoWayConstantProductPair.TradingStatus", + "name": "status", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "yesTokenId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ] } } } diff --git a/augurScan/config/manifests/mainnet.json b/augurScan/config/manifests/mainnet.json index 0f053542c..c4c0539cc 100644 --- a/augurScan/config/manifests/mainnet.json +++ b/augurScan/config/manifests/mainnet.json @@ -15,6 +15,8 @@ ["0x26CA07838f50Fd03d9b586f8780F42De9877286f", "Escalation Claim Checkpoint Delegate", "escalationGameClaimDelegate"], ["0xEc0d2a449F6Ec70998043b18F680bC42Cf2f3249", "Escalation Game Factory", "escalationGameFactory"], ["0x3A7b70613E86908a4494D3cBf627C62342c96990", "Security Pool Factory", "securityPoolFactory"], + ["0xdCC03cc07Ec0c633079446Be13b52371104ff0d7", "Security Pool Forker", "securityPoolForker"], + ["0x5dae4d3F03A59a51F01e52920a76Cb4013D15c70", "Security Pool Factory", "securityPoolFactory"], ["0xb077F2f7a41d52a0dBe03d1d3Ff7fa1ad7816505", "Escalation Game Proof Verifier", "escalationProofVerifier"], ["0x221657776846890989a759ba2973e427dff5c9bb", "Genesis REP", "reputationToken"], ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "Wrapped Ether", "weth"], diff --git a/augurScan/config/manifests/sepolia.json b/augurScan/config/manifests/sepolia.json index 682478340..592961f1f 100644 --- a/augurScan/config/manifests/sepolia.json +++ b/augurScan/config/manifests/sepolia.json @@ -15,6 +15,8 @@ ["0x26CA07838f50Fd03d9b586f8780F42De9877286f", "Escalation Claim Checkpoint Delegate", "escalationGameClaimDelegate"], ["0xEc0d2a449F6Ec70998043b18F680bC42Cf2f3249", "Escalation Game Factory", "escalationGameFactory"], ["0x49De932c947343ADb87508280f99251A3b326283", "Security Pool Factory", "securityPoolFactory"], + ["0x95780f1A1DB83017Accbc85F3afcfc5057d3BEcB", "Security Pool Forker", "securityPoolForker"], + ["0xFda4f0959819d8e73cdAC8E665Bff73541151ac9", "Security Pool Factory", "securityPoolFactory"], ["0xb077F2f7a41d52a0dBe03d1d3Ff7fa1ad7816505", "Escalation Game Proof Verifier", "escalationProofVerifier"], ["0x13CD80A6741A6349d2FeBDd65991108eCfD4f283", "Genesis REP", "reputationToken"], ["0x65156FD21726b8efcB627fa38c506E3f3542F601", "Wrapped Ether", "weth"], diff --git a/augurScan/package.json b/augurScan/package.json index 78084b9fd..fa3483a22 100644 --- a/augurScan/package.json +++ b/augurScan/package.json @@ -25,7 +25,6 @@ "devDependencies": { "@biomejs/biome": "2.3.11", "@types/bun": "1.3.11", - "solc": "0.8.35", "typescript": "5.8.2" }, "knip": { diff --git a/augurScan/scripts/snapshot-project-metadata.ts b/augurScan/scripts/snapshot-project-metadata.ts index a6af6b613..628ff602e 100644 --- a/augurScan/scripts/snapshot-project-metadata.ts +++ b/augurScan/scripts/snapshot-project-metadata.ts @@ -1,10 +1,10 @@ import { createHash } from 'node:crypto' import { readdir, readFile } from 'node:fs/promises' import path from 'node:path' -import solc from 'solc' const projectRoot = path.resolve(import.meta.dir, '../..') const contractsRoot = path.join(projectRoot, 'solidity/contracts') +const compiledArtifactPath = path.join(projectRoot, 'solidity/artifacts/Contracts.json') const outputPath = path.resolve(import.meta.dir, '../config/abis.json') const manifestsRoot = path.resolve(import.meta.dir, '../config/manifests') @@ -31,74 +31,64 @@ const vendorPrefix = 'contracts/statoblast/openOracle/openzeppelin/contracts/' for (const [name, source] of Object.entries(sources)) { if (name.startsWith(vendorPrefix)) sources[`@openzeppelin/contracts/${name.slice(vendorPrefix.length)}`] = source } -const input = { - language: 'Solidity', - sources, - settings: { - outputSelection: { - '*': { - '*': ['abi'], - }, - }, - }, +const result = JSON.parse(await readFile(compiledArtifactPath, 'utf8')) as { + contracts?: Record> +} +if (result.contracts === undefined) throw new Error(`Canonical Solidity artifact has no contracts: ${compiledArtifactPath}`) + +const abiKeyOrder = ['anonymous', 'indexed', 'components', 'inputs', 'internalType', 'name', 'outputs', 'stateMutability', 'type'] as const +const abiKeyPriorities = new Map(abiKeyOrder.map((key, index) => [key, index])) +function canonicalizeAbiValue(value: unknown): unknown { + if (Array.isArray(value)) return value.map(canonicalizeAbiValue) + if (typeof value !== 'object' || value === null) return value + const entries = Object.entries(value).sort(([left], [right]) => { + const leftIndex = abiKeyPriorities.get(left) + const rightIndex = abiKeyPriorities.get(right) + if (leftIndex === undefined && rightIndex === undefined) return left.localeCompare(right) + if (leftIndex === undefined) return 1 + if (rightIndex === undefined) return -1 + return leftIndex - rightIndex + }) + return Object.fromEntries(entries.map(([key, entryValue]) => [key, canonicalizeAbiValue(entryValue)])) } -const result = JSON.parse(solc.compile(JSON.stringify(input))) as { - contracts?: Record> - errors?: readonly { severity: string; formattedMessage: string }[] +function canonicalizeAbi(abi: readonly unknown[]): readonly unknown[] { + const canonical = canonicalizeAbiValue(abi) + if (!Array.isArray(canonical)) throw new Error('ABI canonicalization did not return an array') + return canonical } -const errors = result.errors?.filter((error) => error.severity === 'error') ?? [] -if (errors.length > 0) throw new Error(errors.map((error) => error.formattedMessage).join('\n')) -if (result.contracts === undefined) throw new Error('Solidity compiler returned no contracts') const contracts: Record = {} for (const source of Object.keys(result.contracts).sort()) { + if (sources[source] === undefined) continue const sourceContracts = result.contracts[source] if (sourceContracts === undefined) continue for (const name of Object.keys(sourceContracts).sort()) { const artifact = sourceContracts[name] - if (artifact === undefined || artifact.abi.length === 0) continue + if (artifact?.abi === undefined || artifact.abi.length === 0) continue + const abi = canonicalizeAbi(artifact.abi) const existing = contracts[name] - if (existing !== undefined && JSON.stringify(existing.abi) !== JSON.stringify(artifact.abi)) { - contracts[`${source}:${name}`] = { source, abi: artifact.abi } + if (existing !== undefined && JSON.stringify(existing.abi) !== JSON.stringify(abi)) { + contracts[`${source}:${name}`] = { source, abi } continue } - contracts[name] = { source, abi: artifact.abi } + contracts[name] = { source, abi } } } -const tradingSources = { - ...(await collectSources(contractsRoot, projectRoot)), - ...(await collectSources(path.join(projectRoot, 'trading/contracts'), projectRoot)), -} -const tradingVendorPrefix = 'solidity/contracts/statoblast/openOracle/openzeppelin/contracts/' -for (const [name, source] of Object.entries(tradingSources)) { - if (name.startsWith(tradingVendorPrefix)) tradingSources[`@openzeppelin/contracts/${name.slice(tradingVendorPrefix.length)}`] = source -} -const tradingResult = JSON.parse( - solc.compile( - JSON.stringify({ - language: 'Solidity', - sources: tradingSources, - settings: { outputSelection: { '*': { '*': ['abi'] } } }, - }), - ), -) as typeof result -const tradingErrors = tradingResult.errors?.filter((error) => error.severity === 'error') ?? [] -if (tradingErrors.length > 0) throw new Error(tradingErrors.map((error) => error.formattedMessage).join('\n')) for (const [source, name] of [ - ['trading/contracts/TwoWayConstantProductFactory.sol', 'TwoWayConstantProductFactory'], - ['trading/contracts/TwoWayConstantProductPair.sol', 'TwoWayConstantProductPair'], + ['contracts/trading/TwoWayConstantProductFactory.sol', 'TwoWayConstantProductFactory'], + ['contracts/trading/TwoWayConstantProductPair.sol', 'TwoWayConstantProductPair'], ] as const) { - const artifact = tradingResult.contracts?.[source]?.[name] - if (artifact === undefined) throw new Error(`Solidity compiler returned no ${name} ABI`) - contracts[name] = { source, abi: artifact.abi } + const artifact = result.contracts?.[source]?.[name] + if (artifact?.abi === undefined) throw new Error(`Canonical Solidity artifact has no ${name} ABI`) + contracts[name] = { source, abi: canonicalizeAbi(artifact.abi) } } const payload = { sourceHash: createHash('sha256') .update( - Object.entries(tradingSources) + Object.entries(sources) .filter(([name]) => !name.startsWith('@openzeppelin/')) .sort(([left], [right]) => left.localeCompare(right)) .map(([name, source]) => `${name}\0${source.content}`) diff --git a/bun-test-setup-ui.ts b/bun-test-setup-ui.ts index 028523294..aecfbee1b 100644 --- a/bun-test-setup-ui.ts +++ b/bun-test-setup-ui.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach } from 'bun:test' -import { resetActiveEnvironmentForTesting } from './ui/ts/lib/activeEnvironment.js' +import { resetActiveEnvironmentForTesting } from './ui/coreShared/ts/lib/activeEnvironment.js' beforeEach(() => { resetActiveEnvironmentForTesting() diff --git a/bun.lock b/bun.lock index 26150582d..2b82ca852 100644 --- a/bun.lock +++ b/bun.lock @@ -28,6 +28,9 @@ }, }, "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", "brace-expansion": "5.0.9", "fast-uri": "3.1.5", "nanoid": "3.3.18", diff --git a/bunfig.toml b/bunfig.toml index f08af8e3c..c3635d983 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,4 +1,4 @@ [test] timeout = 300000 preload = ['./bun-test-setup.ts'] -pathIgnorePatterns = ['solidity/js/tests/**', 'ui/js/tests/**'] +pathIgnorePatterns = ['solidity/js/tests/**', 'ui/coreShared/js/tests/**', 'ui/zoltar/js/tests/**', 'ui/statoblast/js/tests/**', 'ui/trading/js/tests/**'] diff --git a/docs/assets/js/docsSearchData.js b/docs/assets/js/docsSearchData.js index ed86fea2a..3952bd02d 100644 --- a/docs/assets/js/docsSearchData.js +++ b/docs/assets/js/docsSearchData.js @@ -1,2 +1,2 @@ // Generated by scripts/build-docs-index.mts. Loaded on demand; do not edit directly. -window.statoblastDocsSearch = [{"fragment":"","heading":"","keywords":["ABI","transactions","callers","events"],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"For developers and operators: find the contract that handles each state change, who can call it, and what the call does. Quick index ZoltarQuestionData Zoltar ReputationToken SecurityPoolFactory SecurityPool SecurityPoolForker EscalationGame LiquidationApprovalRegistry OpenOraclePriceCoordinator ShareToken UniformPriceDualCapBatchAuction","title":"Contract interactions","topic":"Contracts","weight":1},{"fragment":"zoltarquestiondata","heading":"ZoltarQuestionData","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates immutable, content-addressed scalar or categorical questions and exposes their display metadata. Source Read surface: Use getQuestionId before submission; questionCreatedTimestamp and questions for direct lookup; getQuestionCount and getQuestions for indexed or paged discovery; and getQuestionEndDate , getOutcomeLabels , splitUint256IntoTwoWithInvalid , hasNonZeroScalarReservedBits , isMalformedAnswerOption , and getAnswerOptionName when validating or displaying answers. In the QuestionData tuple, startTime and endTime are uint48 , while numTicks is uint120 ; clients must use these exact widths because they determine the getQuestionId and createQuestion selectors. Transaction Caller Main prerequisites State or asset effect Primary signals createQuestion(questionData, outcomeOptions) Anyone Question ID not already created; end time is on or after start time. Scalar questions use no labels, require display maximum greater than minimum, and positive ticks. Categorical questions require nonempty labels whose keccak256(abi.encode(label)) values are strictly descending. Stores the question at its deterministic content hash, records the creation timestamp, appends it to discovery order, and stores categorical labels when supplied. QuestionCreated","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"zoltar","heading":"Zoltar","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Registers universe forks, charges the fork admission haircut, and mints branch-specific child REP. Source Read surface: Use universes , forkThresholdDivisor , forkBurnDivisor , zoltarQuestionData , genesisReputationToken , getForkTime , forkQuestionMatches , getRepToken , getForkThresholdAttoRep , getNonDecisionThresholdAttoRep , getUniverseTheoreticalSupplyAttoRep , getChildUniverseId , getDeployedChildUniverses , and getMigrationRepBalanceAttoRep to reconstruct universe and migration state. Construction requires a deployed genesis REP token with theoretical supply from one attoREP through 11 million REP and forkBurnDivisor >= 5 , which caps the uncredited fork haircut at 20% of the threshold. Security boundaries for these calls are A15 intended question selection and A25 safe immutable parameters . Transaction Caller Main prerequisites State or asset effect Primary signals forkUniverse(universeId, questionId) Any address able to fund the current fork threshold Initialized and unforked universe; existing ended question; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Records the fork, removes threshold REP from the parent universe, and credits the caller with the threshold minus the configured uncredited haircut. UniverseForked burnRep(universeId, amountAttoRep) Any REP holder; the caller can burn only its own balance Initialized universe; positive amount; sufficient caller REP and theoretical supply. Genesis REP requires allowance. Permanently removes REP without creating migration credit; escalation settlement uses this when the haircut was not paid through its own fork. RepBurned and the token burn or transfer event deployChild(universeId, outcomeIndex) Anyone Parent forked; outcome is well formed; child is not already deployed. Deploys the deterministic child REP token and initializes the child universe. DeployChild addRepToMigrationBalance(universeId, amountAttoRep) Parent REP holder Universe forked; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Burns or sinks additional parent REP and increases the caller's reusable migration balance. MigrationRepAdded splitMigrationRep(universeId, amountAttoRep, outcomeIndexes) Migration-balance holder Universe forked. A nonempty list additionally requires every outcome to be well formed and the cumulative amount per child not to exceed the caller's migration balance. Mints amount of child REP into every selected branch, deploying missing children lazily. An empty outcome list returns after the universe-fork guard without outcome validation, deployment, minting, or events. A nonempty zero-amount call still validates every outcome, may deploy missing children, performs zero-value child REP mints, and records a zero split for every branch. TheoreticalSupplySet and DeployChild when needed; child REP Transfer and Mint , then MigrationRepSplit , per selected branch, including at zero amount; no event for an empty list","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"reputationtoken","heading":"ReputationToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Implements universe-specific ERC-20 REP and enforces the supply ceiling maintained by Zoltar. Source Read surface: Use getTotalTheoreticalSupplyAttoRep , zoltar , and the standard ERC-20 name , symbol , decimals , totalSupply , balanceOf , and allowance reads. Transaction Caller Main prerequisites State or asset effect Primary signals setMaxTheoreticalSupplyAttoRep(totalTheoreticalSupplyAttoRep) Zoltar only Called by Zoltar as part of child-universe creation; theoretical supply does not exceed 11 million REP. Sets the child token theoretical-supply ceiling used to bound subsequent migration mints. TheoreticalSupplySet mint(account, valueAttoRep) Zoltar only account is nonzero; resulting ERC-20 supply does not exceed theoretical supply. Mints branch REP to an account. Mint and ERC-20 Transfer burn(account, valueAttoRep) Zoltar only account is nonzero and has sufficient REP; theoretical supply covers the burn. Burns account REP and reduces both actual and theoretical supply by the same amount. Burn and ERC-20 Transfer transfer(to, value) REP holder Destination is nonzero; caller has sufficient balance. Moves REP from the caller without changing actual or theoretical supply. Transfer approve(spender, value) Any REP account setting its own allowance Spender is nonzero. Replaces the named spender allowance without moving REP. Approval transferFrom(from, to, value) A spender with sufficient allowance from from Source and destination are nonzero; source has sufficient balance; caller has sufficient allowance, including when caller equals source. Moves REP from from ; a finite allowance decreases by value , while an infinite allowance remains unchanged. Neither allowance path emits Approval . Transfer only","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolfactory","heading":"SecurityPoolFactory","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates and canonically registers origin and child security pools with their share token, oracle coordinator, and optional truth auction. Source Read surface: Use initialEscalationGameDepositAttoRep , minimumSecurityBondDebtAttoEth , and minimumVaultRepDepositAttoRep for immutable deployment floors. The factory requires the escalation baseline to equal 1 REP, so each pool fixes its effective escalation deposit at construction as exactly max(1 REP, theoretical REP supply / 10,000,000) . A zero configured vault REP floor selects the default theoretical REP supply / 100,000 ; a nonzero constructor value is the exact override. The security-bond debt floor defaults to 1 ETH. Use securityPoolDeploymentCount with the strict securityPoolDeploymentsRange(startIndex, count) pager, which reverts rather than truncating when the requested range exceeds the array. Use getOriginId , getPoolId , getSecurityPool , getSecurityPoolOriginId , and getSecurityPoolHasInheritedForkOutcome for canonical lookup. Transaction Caller Main prerequisites State or asset effect Primary signals deployOriginSecurityPool(universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas) Anyone statoblastSecurityMultiplierBps > 10_001 , which makes the halfway migration component strictly greater than one; the effective pool-held vault REP backing multiplier separately floors that component at the 10,500-BPS liquidation-award reserve described by the liquidation design . initialReportPriorityFeeAttoEthPerGas > 0 and remains within the coordinator-computed OpenOracle uint128 report/escalation-halt capacity bound; question exists and has exactly the categorical labels Yes , then No ; universe is unforked and has a REP token; the non-decision threshold exceeds the construction-time effective escalation deposit max(1 REP, theoretical REP supply / 10,000,000) ; the origin/universe/priority-fee slot has not already been claimed. Creates the canonical origin pool, its lineage-wide share token, and its price coordinator with the configured initial-report priority fee, then wires and registers them atomically. SecurityPoolRegistered , then DeploySecurityPool deployChildSecurityPool(parent, shareToken, universeId, questionId, statoblastSecurityMultiplierBps, currentRetentionRate, settlementCollateralAttoEth) SecurityPoolForker only Parent is the canonical pool for its lineage; supplied share token equals the parent share token; target origin/universe slot is unclaimed; deployment arguments satisfy downstream constructors and wiring. Creates and registers a canonical child pool with a coordinator that inherits initialReportPriorityFeeAttoEthPerGas from the parent coordinator and a forker-owned truth auction, while retaining the parent lineage share token. SecurityPoolRegistered , then DeploySecurityPool","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypool","heading":"SecurityPool","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Holds ETH collateral and REP underwriting, accounts for vaults and fees, mints shares, and routes local escalation. Source Read surface: Immutable relationship and configuration getters are questionId , universeId , initialEscalationGameDepositAttoRep , zoltar , parent , shareToken , repToken , priceOracleManagerAndOperatorQueuer , openOracle , escalationGameFactory , questionData , securityPoolForker , truthAuction , securityPoolFactory , and statoblastSecurityMultiplierBps ; the current game is escalationGame . Accounting getters include totalCapacityOwnershipAttoRep , settlementCollateralAttoEth , totalRepBackingUnits , shareTokenSupplyAttoShares , securityVaults , minimumSecurityBondDebtAttoEth , minimumVaultRepDepositAttoRep , totalBadDebtAttoEth , and vaultBadDebtAttoEth . lastDepositTargetHealthFactorBpsByVault is deposit-instruction metadata, not a vault health measurement. Use getVaultCapacityBackingFactorsBps for current associated and pool-held REP-per-capacity ratios, getCurrentMintingCapacityAttoEth for price-converted aggregate capacity, and getVaultOpenInterestAttoEth for a vault’s live proportional obligation. Other derived and paged reads are getVaultCount , getVaults , attoSharesToAttoEth , attoEthToAttoShares , attoRepToBackingUnits , backingUnitsToAttoRep , getTotalPoolHeldAttoRep , totalAccruedFeesAttoEth , getPoolAccountingSnapshot , getVaultFeeRemainder , and isEscalationResolved . The backing-factor ratios are not current vault health: associated REP includes dispute-staked principal as at-risk security, while current health also depends on OI, REP/ETH price, the security multiplier, and both protocol constraints. The vault registry is append-only and newest-registered first. Registration requires only a nonzero address and can occur without economic state; consumers filter current positions from securityVaults , escalation stake, and bad debt. isEscalationResolved() is true only when a local escalation game is configured and the forker routes a non- None outcome; an operational fixed-outcome child without a local game returns false. Lifecycle and fee getters are totalClaimableVaultFeesAttoEth , lastUpdatedFeeAccumulator , feeIndex , currentRetentionRate , awaitingForkContinuation , and systemState . Price-sensitive withdrawal, dynamic-capacity, and liquidation calls depend on A16 timely inclusion , A21 genesis REP and WETH behavior , A19 observable correctable price , and A06 lifecycle executors . User-initiated pool calls additionally depend on A28 account authority . Transaction Caller Main prerequisites State or asset effect Primary signals burnEscalationWinnerHaircut(amountAttoRep) This pool's EscalationGame only Caller is the configured escalation game; amount is positive and the game has already transferred enough REP to the pool. Burns the winning-deposit haircut from REP already escrowed in the game. RepBurned and ERC-20 Transfer ; child REP also emits Burn depositRepToVault(attoRepAmount, targetHealthFactorBps) Vault owner Operational and unforked; isEscalationResolved() is false; deposit amount is positive; deposit target factor is at least 10,000; resulting vault REP meets the configured supply-scaled minimum. Transfers REP into the pool, credits proportional REP backing units, and creates REP-denominated fee-earning capacity ownership from this deposit and its selected deposit target factor. RepDepositedToVault , VaultDepositTargetHealthFactorRecorded , and accounting checkpoints redeemFees(vault) Anyone; any nonzero ETH payment is always sent to vault A nonzero payment path requires vault to accept ETH. First accrues the vault's fees. If resulting claimable fees are zero, returns without payment; otherwise clears and pays the full amount. Accrual checkpoints only when accrual state changes; both VaultAccountingCheckpoint and PoolAccountingCheckpoint for a nonzero redemption; no event when fees and accrual state are unchanged createCompleteSet() with ETH Trader Operational and unforked; isEscalationResolved() is false; not awaiting continuation; positive ETH converts to at least one complete-set unit; live oracle-priced minting capacity covers the resulting settlement collateral, not merely this deposit; any explicit unassigned auction position remains healthy after the mint; under A22 asset-recipient compatibility , a contract trader accepts onERC1155BatchReceived . Adds collateral and mints one Invalid , Yes , and No share per complete-set unit, then invokes the ERC-1155 batch-receiver callback for a contract trader. Callback rejection rolls back the ETH, pool accounting, events, and share mint. CompleteSetCreated , PoolAccountingCheckpoint , then ERC-1155 TransferBatch on a successful callback redeemCompleteSet(amountAttoShares) Anyone; positive redemption requires the caller to hold the complete set Operational and unforked; caller holds every outcome amount requested; caller accepts the resulting ETH call, including zero value. Zero is accepted without a token balance. Burns equal balances of all three outcomes and pays amountAttoShares * settlementCollateralAttoEth / shareTokenSupplyAttoShares using the pool's remaining economic claim supply as its collateral denominator. Complete-set issuance adds to that denominator, while complete-set and winning-share redemption consume it; fork-time source entitlements materialize without changing it because their claims are already reserved. Zero passes the token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. CompleteSetRedeemed and PoolAccountingCheckpoint redeemShares() Anyone; a positive payout requires the caller to hold winning shares Operational pool with a final outcome; caller accepts the resulting ETH call, including zero value. Burns the caller's full winning balance and pays its pro-rata remaining collateral. A zero winning balance passes token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. SharesRedeemed and PoolAccountingCheckpoint redeemRepFromVault(vault) Anyone; REP is always sent to vault Operational pool with a final outcome; the specified vault has no escalation escrow and has redeemable REP. Burns the vault's REP backing units and returns its proportional vault REP backing. RepRedeemedFromVault depositToEscalationGame(outcome, maxAmount) Vault owner Question end has passed; pool operational in an unforked universe, without an inherited fixed outcome, and not awaiting continuation. On the first deposit, the live non-decision threshold must exceed one attoREP; outcome and amount accepted; the remaining vault and aggregate pool totals each preserve both live open-interest health branches; a fresh price is required when total capacity ownership is nonzero. Deploys the local game on the first deposit. The game factory uses the configured start bond while it is below the live non-decision threshold; if tracked REP supply later makes it too large, the factory uses nonDecisionThresholdAttoRep - 1 instead. Repeat deposits use the existing game's stored startBondAttoRep and nonDecisionThresholdAttoRep . Every accepted deposit removes enough REP backing units and escrows dispute-staked REP on the selected outcome. EscalationGameSet on first deposit; DepositToEscalationGame withdrawFromEscalationGame(outcome, depositIndexes) Anyone; a nonempty list must select deposits belonging to one original depositor Game configured; operational pool; valid final outcome. If an external fork interrupted the game, parent withdrawal stays unavailable: winners settle in the child by carried proof, inherited losers require no transaction, and unresolved parent escalation-deposit accounting cleanup is optional. A nonempty list additionally requires valid local indexes and one common depositor. A nonempty list settles local deposits and pays winning REP to the immutable depositor recorded by each deposit. Liquidation cannot change that payout address. An empty list returns after the outer lifecycle checks without settlement, state change, or event. Per processed deposit, escalation-game CarryDepositConsumed ; additionally ClaimDeposit for a winning payout. No event for an empty list withdrawForkedEscalationDeposits(outcome, proofs) Anyone; a nonempty list must name one original depositor across all proofs Game configured; operational child pool; valid final outcome. A nonempty list additionally requires an initialized and fully resumed continuation game, valid unconsumed winning proofs, and one common depositor. A nonempty list verifies and consumes carried proofs, then pays winning child REP to the immutable depositor committed in each leaf. Stable continuation identities retain the creating game, and the cumulative retention-index ratio applies every intervening auction haircut in constant ancestry work. An empty list returns after the outer lifecycle checks without proof verification, state change, or event. Per processed proof, escalation-game CarryDepositConsumed and ClaimDeposit . No event for an empty list updateSettlementCollateral() Anyone No caller or lifecycle restriction. It returns unchanged when the accumulator is already at or beyond the clamped timestamp. Accrues elapsed fees through question end while this pool's universe remains unforked; after that universe forks, its fork timestamp replaces question end as this pool epoch's cutoff, including a later question-end-to-fork interval. The cutoff is local to this pool: an activated child starts a separate fee epoch. It moves whole credited fees from settlement collateral into the unallocated accrued-fee reserve and advances the accumulator. With positive elapsed time but zero fee-eligible capacity ownership it clears denominator-specific remainder and advances the timestamp without charging fees. PoolAccountingCheckpoint whenever positive elapsed time is processed, including the zero-capacity-ownership branch; no event for an unchanged timestamp updateRetentionRate() Anyone No caller restriction. It returns unchanged when the pool is not Operational or the calculated rate equals the stored rate. Zero live minting capacity selects the maximum retention rate. Recalculates the retention rate from current collateral and live oracle-priced minting capacity. PoolAccountingCheckpoint only when the stored retention rate changes; no event for a no-op updateVaultFees(vault) Anyone for any address No caller, nonzero-vault, or lifecycle restriction. First updates pool accrual, then advances the vault fee index and fractional remainder, moves whole assigned fees from reserve to the vault, registers any previously unseen nonzero vault address regardless of economic state, and returns leftover reserve to settlement collateral once a forked pool has checkpointed all fee-eligible capacity ownership. Accrual PoolAccountingCheckpoint when due; VaultAccountingCheckpoint when the vault index, remainder, or claimable fee balance changes; an additional PoolAccountingCheckpoint when pool accounting changes; no event when neither accrual nor vault or pool accounting changes withdrawRepFromVault(vault, attoRepAmount) This pool's OpenOraclePriceCoordinator only Fresh coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; no vault REP escrow; the remaining vault and aggregate pool totals each meet the upward-rounded associated-REP and free-REP backing requirements, with equality healthy. Removes the requested proportional REP backing units, or all backing units when the requested remainder would fall below the REP minimum; proportionally reduces the vault and pool capacity ownership; recalculates retention; and transfers the resulting withdrawable REP to vault . REP Transfer ; RepWithdrawnFromVault ; VaultAccountingCheckpoint ; and applicable fee-accrual or retention PoolAccountingCheckpoint events performLiquidation(request) This pool's OpenOraclePriceCoordinator only In ABI order, request contains operationId , operator , receiverVault , targetVault , requestedDebtAttoEth , snapshot , minimumReceiverHealthFactorBps , and minLiquidationPriceDistanceBps . The nested snapshot contains targetBackingUnits , targetCapacityOwnershipAttoRep , totalPoolHeldAttoRep , and totalRepBackingUnits . Fresh settled coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; receiver differs from target. The target backing and capacity-ownership snapshot fields must match; the two pool-total snapshot fields are reconstruction evidence, while execution uses live pool totals. After target and receiver fee checkpoints, the liquidation delegate requires live target backing, dispute-staked REP, and open interest to remain at least minLiquidationPriceDistanceBps beyond the liquidation threshold and requires the live target state to remain unhealthy. When debt moves, the receiver must satisfy the protocol backing checks multiplied by its approved minimum health factor, using live post-liquidation state and upward-rounded requirements; its resulting debt must meet the configured debt floor and its REP must meet the vault floor. The target resulting debt must be zero or meet the debt floor; when debt remains, target REP must meet the vault floor. Capped by the target vault's open interest and fundable REP award, a nominal debt quote selects proportional capacity ownership rounded downward and moves that ownership to the explicitly selected receiver vault. Moved security-bond debt is the receiver's exact live open-interest increase and cannot exceed the nominal quote or request. On a delegated route, the coordinator additionally bounds it by the staged approval reservation; the self-receiving route has no approval reservation. The operator only submits the transaction. Dispute-staked REP claims, accrued claimable fees, surplus vault REP backing, and unmatched ownership remain with the target. On a full-target request, target open interest minus exact moved debt is recorded as attoETH-denominated bad debt; that residual can include both an award-unfunded slice and integer-allocation residue. Receiver or target dust cannot turn otherwise funded debt into bad debt. Fee-accrual and target or receiver VaultAccountingCheckpoint events as needed; VaultLiquidated identifies operation, operator, receiver, target, moved debt, moved ownership, and bad debt; VaultBadDebtRecorded records residual target debt on a full-target request; final pool accounting checkpoint setStartingParams(...) SecurityPoolFactory only Factory caller. The pool has no internal one-shot or lifecycle guard; the factory exposes it only through atomic deployment wiring. Sets the fee timestamp, retention, and collateral, seeds the coordinator with zero for an origin or the parent's last price for a child, then checkpoints initialization. Coordinator RepEthPriceSet and CoordinatorStateCheckpoint , then pool PoolAccountingCheckpoint , even for zero or repeated values if the factory were to call again activateForkMode() SecurityPoolForker only The pool has no inherited fixed outcome, so a fixed child cannot reopen for a later universe fork. There is no current-state guard otherwise. A configured game's drain must succeed or the entire activation reverts without propagating its reason data. Sets PoolForked , accrues through the fork clamp, transfers the pool's entire REP balance to the forker, then makes the pool drain its configured escalation game's entire REP balance to the forker. Repeated calls are not lifecycle-guarded and transfer any balances replenished since the prior call before repeating the checkpoints. Pool-held REP Transfer always, including at zero; configured-game REP Transfer only for a positive game balance; accrual checkpoint when due; always PoolForkModeActivated and fork-activation PoolAccountingCheckpoint initializeForkedEscalationGame(...) SecurityPoolForker only No game is configured; downstream startFromFork parameters are valid. Deploys and starts the pool's paused fork-continuation game with inherited timing and optional fixed outcome. Escalation GameContinuedFromFork , then pool EscalationGameSet initializeForkCarrySnapshotWithResolutionBalances(...) SecurityPoolForker only A game is configured; it is a fork continuation with no prior snapshot; leaf counts fit the MMR; supplied or computed snapshot ID matches the data. Installs the continuation game's immutable carry peaks, counts, totals, resolution balances, and normalized nullifier roots. ForkCarryCheckpoint resumeForkedEscalationGame() Anyone Pool is operational, awaiting a configured fork continuation, and the game has not resumed. Checks the already-installed immutable carry commitment and aggregate REP funding, clears the pool wait flag, records the resume timestamp, and starts the continuation's remaining escalation clock in one bounded call. ForkContinuationResumed and AwaitingForkContinuationSet(false) setAwaitingForkContinuation(shouldAwait) SecurityPoolForker only No lifecycle or value-change guard. Stores whether complete-set minting must wait for continuation initialization. AwaitingForkContinuationSet , including for a repeated value setSystemState(newState) SecurityPoolForker only No transition or value-change guard. Replaces the pool lifecycle state directly. SystemStateSet , including for a repeated state configureVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, lastDepositTargetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth) SecurityPoolForker only vault is nonzero; no lifecycle or value-change guard. Replaces the vault REP backing units, price-independent capacity ownership, fee index, latest-deposit preference metadata, vault bad debt, and aggregate pool bad debt, clears pooled fee-index remainder when capacity ownership changes, and registers the nonzero vault address regardless of the supplied state. Always VaultAccountingCheckpoint and PoolAccountingCheckpoint , including when all supplied values repeat current state configureFinalizedAuctionVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, lastDepositTargetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth) SecurityPoolForker only vault is nonzero; the forker has already included the sold capacity ownership in the pool fee denominator at auction finalization. Assigns finalized-auction REP backing and already-fee-eligible capacity ownership to the winning vault without clearing the pool-wide fee-index remainder, preserves its latest-deposit preference metadata without inventing one for a new vault, then replaces the vault fee index, bad debt, and aggregate pool bad debt and registers the vault. Always VaultAccountingCheckpoint and PoolAccountingCheckpoint , including when all supplied values repeat current state setTotalRepBackingUnits(newDenominator) SecurityPoolForker only No lifecycle or value-change guard. Replaces the REP backing units denominator. TotalRepBackingUnitsSet , including for zero or a repeated value setTotalSharesAttoShares(newTotalSharesAttoShares) SecurityPoolForker only No lifecycle or value-change guard. Replaces stored shareTokenSupplyAttoShares , the denominator used by attoSharesToAttoEth and complete-set redemption. ShareTokenSupplySet , including for zero or a repeated value setPoolFinancials(newSettlementCollateralAttoEth, newTotalCapacityOwnershipAttoRep, newFeeEligibleCapacityOwnershipAttoRep, newTotalBadDebtAttoEth) SecurityPoolForker only Fee-eligible capacity ownership does not exceed total capacity ownership, and the supplied settlement collateral does not exceed the current price-converted minting capacity; no lifecycle or value-change guard. Replaces settlement collateral, both price-independent capacity-ownership totals, and aggregate pool bad debt, resets the fee timestamp to the current block, and clears fee-index rounding carry. PoolAccountingCheckpoint , including for repeated financial values authorizeChildPool(pool) SecurityPoolForker only This parent pool is already authorized; candidate reports this share token; candidate universe has no different canonical pool. No pool-lifecycle guard. Asks the lineage share token to establish pool as the canonical authorized pool for its universe; reauthorizing the same pool is a no-op. AuthorizationUpdated only on first authorization; no event when already authorized transferEth(receiver, amountAttoEth) SecurityPoolForker only Fee liabilities are covered; amount fits both unreserved pool ETH and tracked settlement collateral; receiver accepts the ETH call, including zero value. Reduces tracked settlement collateral by amount , checkpoints the reconciliation, and calls receiver with that ETH. At zero amount it reduces no settlement collateral but still emits the checkpoint and performs a zero-value call; callback rejection rolls back the transaction and checkpoint. PoolAccountingCheckpoint , including at zero amount; no dedicated ETH-transfer event assignFinalizedAuctionFees(vault, amountAttoRep, auctionFeeIndexAtFinalization) SecurityPoolForker only The supplied finalization index does not exceed the current fee index; the forker credit workflow has already checkpointed the vault and added the claimed capacity ownership. Assigns to the claiming vault the fees its auctioned capacity ownership accrued from truth-auction finalization through the current fee index, combines fractional carry with the vault remainder, and removes whole assigned fees from the unallocated reserve. The ownership was already included in the fee denominator and total ownership at finalization. This call preserves aggregate fees and settlement collateral; after every eligible ownership is reconciled on a forked pool, a subsequent permissionless updateVaultFees checkpoint returns any reserve that no vault can individually claim to settlement collateral. VaultAccountingCheckpoint and auction-claim PoolAccountingCheckpoint ; the calling forker emits ClaimAuctionProceeds only after the broader credit workflow completes Direct ETH transfer to receive() Forker, this pool's truth auction, or parent pool only Sender is one of the three authorized protocol addresses. Forced ETH bypasses this ordinary-call guard. Accepts protocol-routed ETH used by migration and auction settlement. Forced ETH remains raw, unaccounted surplus rather than settlement collateral or fees. No dedicated receive event; the calling protocol step emits its own event","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolforker","heading":"SecurityPoolForker","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Freezes parent pools, creates selected child pools, migrates vault and escalation state, and settles collateral-repair auctions. Source Read surface: Use zoltar , forkData , getMigratedAttoRep , getForkActivationTime , getUnassignedPosition , getUnassignedPositionFeeIndex , isEscalationDepositClaimedDirectly , getEscalationDepositId , getDirectlyClaimedEscalationPrincipal , isEscalationWinnerHaircutPaidByFork , getEscalationMigrationEntitlementStatus , getOwnForkRepBuckets , getOwnForkMigrationStatus , getMigrationProxyAddress , getQuestionOutcome , attoRepToBackingUnits , and backingUnitsToAttoRep to reconstruct fork progress and preview migration conversions.","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"child-game-trust-boundary","heading":"Child-game trust boundary","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Fork entrypoints and child setup may receive contracts through unauthenticated pool lineages. External-universe initiation requires the supplied pool to be authorized by its declared share token, but that relationship alone does not prove factory registration; own-game initiation does not perform that authorization check. Canonicality comes from the configured SecurityPoolFactory registry. A game relationship check is point-in-time: the reported nonzero game address must return the supplied pool or child from securityPool() when validated. This does not prove that an arbitrary game getter is immutable or that the address was factory-deployed. Child setup captures one reported game address, validates it before privileged use, and reuses that exact address for continuation backing and escrow work. When unresolved escalation requires a continuation and setup initially reports no game, initialization creates one; the forker then captures and validates it before continuation use. Combined vault migration passes the captured child/game pair into unresolved cleanup without reading the child getter again. Truth-auction completion performs a fresh point-in-time validation of the game reported then before checking continuation readiness. Genuine factory-deployed EscalationGame instances store their pool immutably, but safety on unauthenticated paths does not assume arbitrary contracts do. Transaction Caller Main prerequisites State or asset effect Primary signals initiateSecurityPoolFork(securityPool) Anyone Pool operational with no inherited fixed outcome; the pool is authorized by its declared share token; its universe already forked; fork state not initialized; if an escalation game exists, it reports the supplied pool from securityPool() when validated and the universe fork occurred before that game settled. Declared-token authorization is not configured-factory registration; see the child-game trust boundary . Freezes the supplied pool after an external universe fork, drains its pool and game REP, and records a migration snapshot keyed by that address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot and ParentRepLocked ; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists forkZoltarWithOwnEscalationGame(securityPool) Anyone Pool operational with no inherited fixed outcome; its escalation game reports the supplied pool from securityPool() when validated and canTriggerOwnFork() is true because it recorded a local non-decision or inherited a threshold tie without a game-level fixed outcome; universe not already forked. The game-local predicate does not bypass the pool guard. Unlike external-universe initiation, this entrypoint does not require declared-share-token authorization; neither path authenticates the supplied address against the configured pool factory. See the child-game trust boundary . Uses the supplied pool game's non-decision to fork Zoltar, freezes that pool, and records own-fork REP buckets and snapshot state keyed by its address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot , ParentRepLocked , and Zoltar fork events; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists migrateRepToZoltar(securityPool, outcomeIndices) Anyone Migration proxy exists and the pool is PoolForked . Only a positive migration amount with at least one selected outcome checks the eight-week window, existing child ForkMigration state, outcome validity, and cumulative split bound. A zero amount skips those checks even when outcome values are supplied. For a positive migration amount and nonempty list, ensures that the forker's recorded pool migration amount has been split into each selected child REP branch. A zero migration amount or empty list returns after the proxy and pool-state guards without per-outcome validation or events. MigrationRepSplit and ChildRepSplit when a selected branch requires a new split; no event for a zero amount, empty list, or already-satisfied branch createChildUniverse(securityPool, outcomeIndex) Anyone Parent in migration window; selected fork outcome is well formed; child pool is not already deployed. The returned auction is nonzero, deployed, and has never been trusted by this forker; the child's fork-data slot is unused; and the child reports the expected parent, universe, source factory, forker, and auction. The selected child's reported nonzero escalation game passes the child-game trust boundary . These relationship checks do not independently prove configured-factory registration. Loads an already deployed child universe and REP token or deploys them when absent, then lazily deploys the selected child pool, coordinator, and auction; authorizes and links the child; captures and validates the child's escalation game; and initializes any continuation snapshot and materializes or sweeps child backing through that validated game. DeployChild only when child REP was absent; always SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , and TotalRepBackingUnitsSet ; AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as continuation and backing state requires migrateVault(securityPool, outcomeIndex) Vault owner for their non-escrowed position Migration window open; the selected child's reported nonzero escalation game passes the child-game trust boundary . The optional unresolved parent escalation-deposit accounting cleanup wrapper calls this function first to migrate transferable vault state. Converts the caller's parent REP backing-unit claim to REP at the fork snapshot and credits that REP amount as child-local backing units; transfers REP-denominated capacity ownership, latest-positive-deposit target preference metadata, and vault bad debt into one child pool; checkpoints but retains claimable fees in the parent vault; and separately routes proportional pool-level settlement collateral while preserving aggregate bad debt. Repeat calls can have no additional REP backing units, capacity ownership, or vault bad debt to move. VaultBadDebtMigrated and VaultMigrationCheckpoint migrateVaultWithUnresolvedEscalation(securityPool, vault, childOutcomeIndex) The named vault owner Migration window open; caller equals vault ; selected child not already recorded for this optional cleanup; the selected child's reported nonzero escalation game passes the child-game trust boundary . First runs ordinary migration for the same vault, which may convert its parent REP backing-unit claim to REP and credit that REP as child-local backing units; transfer capacity ownership, latest-positive-deposit target preference metadata, and vault bad debt to the selected child while preserving aggregate bad debt; checkpoint but retain claimable fees in the parent vault; and separately route proportional pool-level settlement collateral. It returns the selected child and its captured, validated escalation game to the unresolved-accounting cleanup phase, which reuses those exact addresses without reading the child's game again. The cleanup then clears that vault's unresolved parent escalation-deposit accounting in constant-size work and records it; the cleanup neither funds dispute-staked REP backing nor authorizes carried proofs. Vault migration events, including VaultBadDebtMigrated , plus EscalationMigrationEntitlementInitialized on first export and EscalationMigrationEntitlementMaterialized for the selected child claimForkedEscalationDeposits(...) The named vault owner Caller equals vault ; unresolved escalation existed when the pool initiated its own fork and the parent game still satisfies canTriggerOwnFork() by having either a local non-decision or an inherited threshold tie without a fixed outcome; selected child can be created or loaded, remains in ForkMigration , has a continuation game that passes the child-game trust boundary , and is inside the eight-week claim window. A nonempty list additionally requires the matching winning outcome, unclaimed deposit identities, and every deposit to commit vault as its immutable depositor. First gets or lazily deploys the selected child universe, REP token, pool, coordinator, and auction, then captures and validates the child's escalation game and uses that same game for continuation backing and escrow payment. A nonempty list claims winning own-fork parent deposits and records their stable identities against descendant replay. An empty list still performs child setup and emits a zero-valued claim summary. DeployChild , SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , TotalRepBackingUnitsSet , AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as setup requires; per claimed deposit, CarryDepositConsumed and ClaimDeposit ; escrow record/export events when REP is paid; always ClaimForkedEscalationDepositsToWallet , including for an empty list startTruthAuction(securityPool) Anyone Child migration window ended; pool is in fork migration; required child REP is available. If unresolved escalation existed at fork, any game reported during immediate completion passes the child-game trust boundary . Copies the frozen parent's remaining economic claim supply into the child, closes migration accounting, and either reopens a fully backed child or starts its repair auction. ShareTokenSupplySet and TruthAuctionStarted ; immediate no-auction completion also emits TruthAuctionFinalized , pool accounting checkpoints, and ForkContinuationResumed for an unresolved continuation finalizeTruthAuction(securityPool) Anyone Truth auction started, its one-week window has passed, msg.value is zero, and migrated collateral plus accepted bid ETH does not exceed current price-converted minting capacity. If unresolved escalation existed at fork, the game reported at completion passes the child-game trust boundary . Finalizes the ended auction, accounts migration-routed settlement collateral plus accepted bid ETH, and records every unmigrated REP backing unit, capacity unit, and proportional bad debt in an explicit nonwithdrawable unassigned position. It activates the child, fixes bidder REP-backing-unit and capacity-ownership rates, and saves the fee index. Positive-purchase auction ownership becomes fee eligible immediately; after a zero-purchase auction, the unassigned capacity remains outside fee eligibility. A nonzero repair contribution is rejected. TruthAuctionFinalized , auction AuctionFinalized , and pool accounting checkpoints; TruthAuctionHaircutApplied when purchased REP removes a positive escalation allocation; ForkContinuationResumed for an unresolved continuation settleAuctionBids(securityPool, vault, claimTickIndices, refundTickIndices) Anyone on behalf of the named bidder vault At least one index; before finalization the claim list must be empty and refund indexes must be eligible; after finalization all indexes must belong to the named vault owner and remain unsettled. Before finalization, refunds only provably losing bids. After finalization, combines claim and refund indexes into one settlement withdrawal and transfers each claim's proportional REP backing units, capacity ownership, bad debt, and finalization-to-claim fees from the unassigned position to the bidder vault. Capacity and bad-debt division dust follows each bid's deterministic cumulative ETH position, so claim order cannot change individual or aggregate settlement. The transfer does not change total capacity, fee eligibility, active open interest, total bad debt, retention, or aggregate accrued fees. A winning dust bid may receive capacity ownership even when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion. Underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited claimAuctionProceeds(securityPool, vault, tickIndices) Anyone on behalf of the named bidder vault Auction finalized. A nonempty list additionally requires every index to belong to the named vault owner and remain unsettled. For a nonempty list, withdraws finalized bid settlements and transfers each claim's proportional REP backing units, capacity ownership, bad debt, and finalization-to-claim fees from the unassigned position to the bidder vault. Capacity and bad-debt division dust follows each bid's deterministic cumulative ETH position, so claim order cannot change individual or aggregate settlement. The transfer does not change total capacity, fee eligibility, active open interest, total bad debt, retention, or aggregate accrued fees. A winning dust bid can receive positive capacity ownership when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion, so recipient code cannot block the subsequent credit. For an empty list, the underlying auction withdrawal returns four zeros and the wrapper exits after the finalization guard without validating bids or the named beneficiary, calling it, changing state, or emitting events. For processed bids, underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited; no event for an empty list initializeChildForkedEscalationGameIfNeeded(parent, child, childEscalationGame) This SecurityPoolForker contract only, through its migration delegate callback External caller is the forker itself; parent and child match the active migration path; a supplied nonzero game passes the child-game trust boundary . Allows delegated migration code to initialize a child continuation while preserving the forker as the authoritative caller and the already captured child-game identity. When unresolved escalation requires a continuation and no game existed, it captures and validates the game created by initialization before any continuation use. ChildDisputeStakedRepMaterialized and escalation-continuation events when initialization is required Direct ETH transfer to receive() A child-pool truth auction trusted by this forker during ChildPoolLinked trustedAuctionAddresses[msg.sender] was set when the forker linked the child and emitted ChildPoolLinked ; configured-factory registration determines whether that lineage is canonical. Accepts auction ETH during forker-controlled auction finalization. No dedicated receive event; auction AuctionFinalized is followed by forker TruthAuctionFinalized and pool accounting checkpoints","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"escalationgame","heading":"EscalationGame","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Escrows outcome REP, raises the running resolution cost, detects non-decision, and settles local or carried deposits. Source Read surface: Base getters are securityPool , repToken , activationTime , nonDecisionThresholdAttoRep , startBondAttoRep , nonDecisionTimestamp , nonDecisionState , forkContinuation , forkElapsedAtStart , forkResumedAt , fixedQuestionOutcome , nodes , disputeStakedRepByVaultAttoRep , totalDisputeStakedAttoRep , truthAuctionRepBeforeAttoRep , truthAuctionRepRemainingAttoRep , cumulativeClaimRetention , and cumulativeClaimRetentionExponent . The claim delegate fallback exposes rootClaimSourceGame , applyInheritedClaimRetention , and applyInheritedSourceStorageBasis . The source-storage-basis read allocates retained carry by cumulative-prefix differences so leaf allocations sum to the aggregate checkpoint. disputeStakedRepByVaultAttoRep is locally attributed current-game escrow used for health; inherited carry remains aggregate commitment state until proof settlement. Use previewDepositOnOutcome , computeIterativeAttritionCostAttoRep , computeTimeSinceStartFromAttritionCostAttoRep , totalCostAttoRep , getEscalationGameEndDate , getQuestionResolution , getFinalQuestionResolution , hasReachedNonDecision , canTriggerOwnFork , getBindingCapitalAttoRep , getOutcomeBalancesAttoRep , getDepositsByOutcome , getDepositsByOutcomeLength , forkCarrySnapshotInitialized , getOutcomeState , getForkCarrySnapshot , getForkCarryRoots , isForkCarryFundingComplete , getCarryLeafPageByOutcome , getProofConsumedCarriedDepositIndexesByOutcome , getLocalUnresolvedPrincipalByVaultAndOutcome , and getForkedEscrowByVaultAndOutcome for calculations, lifecycle authorization, pages, carry state, and escrow. Ordinary users route deposits and withdrawals through SecurityPool . Transaction Caller Main prerequisites State or asset effect Primary signals start(startBondAttoRep, nonDecisionThresholdAttoRep) EscalationGameFactory contract during atomic deployment Game not already started; threshold exceeds the positive start bond. Positive attoREP values are valid. Initializes a local game and sets activation three days after deployment. For ordinary pool games, the factory lowers an oversized configured bond to nonDecisionThresholdAttoRep - 1 before this call. GameStarted startFromFork(startBondAttoRep, nonDecisionThresholdAttoRep, elapsedAtFork, fixedQuestionOutcome, winnerHaircutPaidByFork, forkCarryInitialBackingAttoRep) Immutable owner ( EscalationGameFactory ) during atomic continuation deployment Game not started; threshold exceeds the positive start bond; inherited elapsed time is no greater than seven weeks. Positive attoREP values are valid. Initializes a paused continuation with inherited elapsed time, an optional fixed matching child outcome, and immutable fork-time haircut/backing accounting. It does not start the remaining clock until resumeFromFork . GameContinuedFromFork resumeFromFork() Owning SecurityPool only Fork-continuation mode; not previously resumed; immutable carry snapshot installed; aggregate REP funding complete. An unrelated fork requires one-to-one backing of effective unresolved principal. For an own-fork continuation, recorded initial backing must be at least sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , where sourcePrincipalAtForkAttoRep is the aggregate raw unresolved principal installed by the snapshot before effective direct-claim deductions. The live balance must cover that initial backing minus child REP already exported by valid direct pre-resume claims. Records the resume timestamp once the immutable carry commitment is installed and funded. The new deadline is max(rebasedCurveEnd, forkResumedAt + 3 days) , so even an exhausted inherited clock receives a fresh response period. After that deadline, getFinalQuestionResolution returns the fixed outcome when the continuation has one. ForkContinuationResumed applyTruthAuctionHaircut(repToRemove) The child pool's SecurityPoolForker only Paused fork continuation; no prior auction haircut; the requested amount is below the game's live REP balance. Transfers the sold child REP to the pool, applies one retention ratio to escrow and outcome balances, and rebases elapsed curve time. The fork remains final and the game remains paused until the pool resumes it. TruthAuctionHaircutApplied and REP Transfer recordDepositFromSecurityPool(...) Owning SecurityPool only Explicit non-decision state is None ; game unresolved; valid outcome; preview and accepted cumulative amount match; room remains below threshold. Appends an accepted local deposit, updates outcome and vault escrow, and records its carry leaf. LocalDepositAppended , DepositOnOutcome , optionally NonDecisionReached withdrawDeposit(uint256 depositIndex, outcome) Owning SecurityPool only Explicit non-decision state is None ; non- None supplied outcome; game final; game and pool final outcomes match; valid unsettled local deposit index. Consumes one local deposit after resolution. A winner pays the deposit's immutable depositor after its haircut; a loser only retires its escrow accounting. CarryDepositConsumed and VaultEscrowUpdated ; for a winner, ClaimDeposit , positive REP payout Transfer , and haircut burn signals when nonzero initializeForkCarrySnapshotWithResolutionBalances(...) Owning SecurityPool only Fork-continuation mode; no prior snapshot; each leaf count fits the MMR; supplied nonzero snapshot ID equals the hash of the normalized data. Installs the immutable inherited peaks, leaf counts, carry totals, resolution balances, and normalized nullifier roots; zero snapshot ID selects the computed ID. Two or more threshold-full inherited balances set nonDecisionState to InheritedThresholdTie without creating a local timestamp. ForkCarryCheckpoint ; additionally InheritedThresholdTie when the installed balances meet the non-decision threshold claimDepositForWinning(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome and valid unsettled local deposit with sufficient escrow. This entrypoint itself does not check final resolution or that the supplied outcome won; its trusted caller selects that path. Consumes a selected local deposit as a winner, consumes its vault escrow, burns the computed haircut when nonzero, and transfers the remaining positive REP payout to the deposit's immutable depositor. CarryDepositConsumed , VaultEscrowUpdated , ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when their amounts are positive claimDepositForWinningWithoutTransfer(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Valid in-range supplied outcome and unsettled local deposit with sufficient escrow. Unlike the transferring form, it has no explicit non- None guard; neither form checks final resolution or that the outcome won. Consumes a selected local deposit and its vault escrow. The depositor's raw escrow backing decreases by the inverse-retention claim units corresponding to the deposit's original principal: the principal itself with no local auction checkpoint, or ⌈originalPrincipal × truthAuctionRepBeforeAttoRep / truthAuctionRepRemainingAttoRep⌉ after a local haircut. Other unconsumed deposits by the same depositor remain backed. The game returns the computed winner amount to the trusted caller but deliberately neither transfers REP nor burns the computed haircut. CarryDepositConsumed , VaultEscrowUpdated , and ClaimDeposit with transferredRep = false ; no REP transfer or haircut burn exportUnresolvedDeposit(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None outcome and a valid unsettled local deposit. Final resolution is not required. Returns deposit identity and amount to the trusted caller while consuming the local deposit from unresolved/escrow accounting without transferring REP. CarryDepositConsumed and VaultEscrowUpdated ; no ClaimDeposit or REP transfer withdrawDeposit(CarriedDepositProof proof, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome; game final and matching the pool final outcome; supplied outcome is the winner; parent deposit was not directly claimed; valid unconsumed Merkle/nullifier proof. Consumes an inherited proof, transfers any positive winning payout, and burns the positive haircut unless the fork already paid it. CarryDepositConsumed and ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when positive exportVaultUnresolvedTotals(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. There is no explicit nonzero-receiver guard: a zero receiver succeeds when the total is zero but the token rejects it when a positive transfer is attempted. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, consumes aggregate unresolved and escrow accounting when positive, and transfers the positive total to repReceiver . Always VaultUnresolvedTotalsExported , including when every amount is zero; VaultEscrowUpdated and REP Transfer only for a positive total exportVaultUnresolvedTotalsWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, and consumes aggregate unresolved and escrow accounting when positive, but leaves token movement to its caller. Always VaultUnresolvedTotalsExported with transferredRep = false , including when every amount is zero; VaultEscrowUpdated only for a positive total; no REP transfer drainAllRep(receiver) Owning SecurityPool only receiver is nonzero; no positive-balance requirement. The protocol reaches this call from the owning pool after activateForkMode enters PoolForked . Transfers the game's full REP balance to receiver . A zero balance returns zero without a transfer or event. REP Transfer for a positive balance; no event at zero balance recordForkedEscrowForOutcome(depositor, outcome, sourcePrincipalAttoRep, childRepAmountAttoRep) Owning SecurityPool or its SecurityPoolForker Outcome is not None ; depositor is nonzero. Source principal and child REP may independently be zero; when both are zero, the call is a no-op. Accumulates source principal and child REP escrow for the depositor and outcome. The depositor remains the immutable payout owner; inherited claims remain in the carry commitment and are not copied into child-local ownership state. When both amounts are zero, returns without changing state or emitting an event. ForkedEscrowRecorded for a nonzero record; no event when both amounts are zero exportForkedEscrowByOutcome(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault and repReceiver are nonzero. Marks every remaining per-outcome escrow amount exported and transfers its positive child REP. When all outcomes were already empty or exported, returns zero arrays without state change, token transfer, or event. ForkedEscrowExported when any source principal or child REP remains; REP Transfer when positive child REP is transferred; no event for an already-empty export exportForkedEscrowByOutcomeWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero. Marks every remaining per-outcome escrow amount exported without transferring child REP. When all outcomes were already empty or exported, returns zero arrays without state change or event. ForkedEscrowExported with transferredRep = false when any source principal or child REP remains; no REP transfer; no event for an already-empty export sweepResidualRepToSecurityPool() Anyone Final outcome; no unresolved principal; no vault escrow; positive residual balance. Returns ordinary-game residual REP to the owning pool. Burns fork-continuation residual so pre-child capital cannot accrue to late or nonexistent child owners. ResidualRepSweptToSecurityPool for an ordinary game; ForkContinuationResidualRepBurned for a fork continuation","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"liquidationapprovalregistry","heading":"LiquidationApprovalRegistry","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores coordinator-local, bounded authorization for a receiver vault to accept liquidation debt from an exact operator. Source Read surface: Use coordinator to identify the validating coordinator and implied security pool. LIQUIDATION_APPROVAL_TYPEHASH , DOMAIN_SEPARATOR , and liquidationApprovalDigest define the chain- and registry-bound EIP-712 message. getLiquidationApproval reports parameters plus available, reserved, consumed, and revoked state; minimumLiquidationApprovalNonce reports receiver invalidation state; liquidationReservations and minimumHealthFactorBps expose operation reservation state and its execution-time health floor. Transaction Caller Main prerequisites State or asset effect Primary signals initialize(coordinator) Anyone while the registry remains uninitialized; normal factory deployment initializes the clone atomically Coordinator is nonzero and the registry has not been initialized. Binds this registry clone to one coordinator and therefore one security pool. No event; the public coordinator getter records the binding. setLiquidationApproval(params) The receiver vault named by params Correct local pool; nonzero receiver and operator; positive cumulative and per-operation limits with per-operation no greater than cumulative; health factor at least 10,000 BPS; live ordered validity window; unused, non-invalidated nonce. Installs explicit onchain bounded approval state and consumes the receiver-scoped nonce. LiquidationApprovalSet permitLiquidationApproval(params, signature) Anyone relaying the receiver vault signature Signature is valid for params.receiverVault ; the chain ID, registry address, stable name/version, pool, receiver, operator, target scope, limits, health factor, window, and nonce are bound by the digest; direct-install validation rules also pass. Validates an EIP-712 EOA or ERC-1271 signature immediately, installs explicit approval state, and consumes the receiver-scoped nonce. LiquidationApprovalSet revokeLiquidationApproval(approvalId) Approval receiver vault only Approval exists and is not already revoked. Prevents new reservations while leaving reservations already attached to staged operations intact. LiquidationApprovalRevoked with available, reserved, and consumed totals invalidateLiquidationApprovalNonce(newNonce) Receiver vault invalidating its own older nonce range New nonce is greater than the receiver current minimum. Raises the minimum nonce accepted for new approval installation or reservation. LiquidationApprovalNonceInvalidated reserve(operationId, approvalId, receiverVault, targetVault, operator, requestedDebtAttoEth, snapshotTargetDebtAttoEth, latestExecutionTimestamp) Bound coordinator only Approval matches local pool, receiver, exact operator, and exact or wildcard target; it is active, unrevoked, non-invalidated, valid through latest execution, and has positive reservable quota. Moves quota from available to pending reserved at staging, bounded by requested debt, target snapshot debt, per-operation limit, and available cumulative quota. LiquidationApprovalReserved release(operationId) Bound coordinator only Coordinator terminal cleanup path. Returns an unsettled delegated reservation to available quota. A missing, self-route, or already settled reservation is a no-op. LiquidationApprovalReleased when quota is returned consume(operationId, debtMovedAttoEth) Bound coordinator only For a delegated reservation, it is unsettled and moved debt does not exceed reserved debt. A self route is a no-op. Permanently consumes exactly moved debt, releases unused reservation, and settles the reservation once. LiquidationApprovalConsumed","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"openoraclepricecoordinator","heading":"OpenOraclePriceCoordinator","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup. Source Read surface: Configuration getters are MAX_PENDING_SETTLEMENT_OPERATIONS , OPEN_INTEREST_DIVIDER , reputationToken , securityPool , openOracle , weth , liquidationApprovalRegistry , gasConsumedOpenOracleReportPrice , gasConsumedSettlement , gasUnitsForOneDispute , initialReportPriorityFeeAttoEthPerGas , targetPriceErrorForDispute , openOracleSecurityMultiplierBps , settlementTime , disputeDelay , protocolFee , feePercentage , multiplier , timeType , trackDisputes , protocolFeeRecipient , escalationHaltMultiplierBps , maxSettlementBaseFeeMultiplierBps , and minLiquidationPriceDistanceBps . Current report and operation getters are pendingReportId , pendingReportSponsor , pendingOperationSlotId , lastSettlementTimestamp , lastPrice , pendingReportMaxSettlementBaseFeeAttoEthPerGas , stagedOperationCounter , and stagedOperations . Use isPriceValid , minimumToken1ReportAttoEth , getRequestPriceCostAttoEth , getQueuedOperationCostAttoEth , getSettlementCallbackGasLimit , getPendingOperationSlot , getActiveStagedOperationCount , getActiveStagedOperations , getPendingSettlementOperationCount , and getPendingSettlementOperationIds for derived or paged state. Report and staged-operation liveness depends on A16 timely inclusion , A17 corrector capability , A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . When lastPrice is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path. Transaction Caller Main prerequisites State or asset effect Primary signals requestPriceIfNeededAndStageLiquidation(targetVault, receiverVault, requestedDebtAttoEth, approvalId, ...) Liquidation operator; a delegated receiver must have approved this exact operator Receiver differs from target; delegated approval matches pool, receiver, operator, and target scope, has available cumulative and per-operation quota, and remains valid through latest execution. Stages explicit operator, receiver, and target roles and reserves bounded receiver quota before any oracle work. The self-receiving operator path uses a zero approval ID. LiquidationRouteStaged ; LiquidationApprovalReserved on a delegated route; staged-operation lifecycle events requestPriceIfNeededAndStageOperation(...) with funding when stale Vault owner for self withdrawal; legacy self-receiving liquidation callers remain supported. While a report is pending, only that report sponsor may stage more operations. securityPool.isEscalationResolved() is false; valid target, nonzero amount, and timeout from 1 second through 5 minutes. Bounty, buffered report funding, matching REP, and token approvals are required only when this call opens a new report. The caller must accept any positive unused-ETH refund. Records the operation, executes immediately with a fresh price, or attaches it to a bounded pending settlement batch and opens a report when required. If unused ETH is positive, the final caller refund uses a low-level callback; rejection rolls back the entire transaction, including any queueing, immediate execution, or newly opened report. StagedOperationQueued , possibly PriceRequested , then ExecutedStagedOperation ; authoritative CoordinatorStateCheckpoint records requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) with report funding Anyone when no fresh price or report is pending Cached price stale; no pending report; nonzero proposed REP/ETH price, ETH bounty, and funding and approvals for at least the configured priority report plus the larger of the base-fee and open-interest WETH reports, plus matching REP. Zero requested WETH uses the minimum; a larger request voluntarily increases the initial report. The caller must accept any positive excess-ETH refund. Opens and atomically funds a fresh WETH/REP report without staging a new operation, then refunds any positive excess ETH through a low-level caller callback. Callback rejection rolls back the report and initial position. PriceRequested and CoordinatorStateCheckpoint executeStagedOperation(operationId) Anyone Operation exists. Expired cleanup requires no valid price; a non-expired operation requires a fresh coordinator price. Lifecycle failures are emitted rather than retried. Consumes an expired operation and releases its delegated reservation without requiring a valid price. Otherwise, consumes and attempts the active operation using the current fresh price. Price-report funding is independent of the operation's notional; the downstream operation applies its own protocol bounds. ExecutedStagedOperation , either LiquidationApprovalConsumed or LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint expireStagedOperation(operationId) Anyone Operation exists and its settlement-plus-validity window has elapsed. Permissionlessly consumes an expired operation and releases its liquidation reservation without requiring a valid oracle price. ExecutedStagedOperation , LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint recoverSettledPendingReport() Anyone A pending report ID exists and its stored OpenOracle storedGame(reportId).settlementTimestamp is nonzero. Clears a pending report whose normal callback path did not clear coordinator state, consumes every live operation attached to that report, and releases each delegated-liquidation reservation. Operations that were active but outside the bounded pending callback batch remain active. PendingReportRecovered , failed ExecutedStagedOperation for each live attached operation, LiquidationApprovalReleased for each attached delegated liquidation, and CoordinatorStateCheckpoint openOracleCallback(...) Configured OpenOracle only Callback report matches the pending report; excessive settlement basefee, a saturated uint24 report counter, an uneconomic final history record at its recorded base fee plus configured priority fee, or zero values reject the price after clearing pending report state. A valid settlement updates the price and auto-executes the bounded pending batch. A terminally rejected settlement consumes the pending batch and releases every liquidation reservation. PriceReported or PriceReportRejected ; operation execution events; authoritative CoordinatorStateCheckpoint records setLiquidationApprovalRegistry(registry) Coordinator deployment factory only Registry is nonzero and no registry was previously installed. Binds the coordinator-local approval registry once. No event; deterministic factory deployment and the public getter identify the registry. setSecurityPool(pool) Anyone while securityPool remains zero; normal factory deployment calls atomically Current securityPool is zero; the argument itself is not required to be nonzero. A nonzero value binds the pool permanently. A zero value emits and checkpoints zero but leaves the setter callable. Normal factory deployment supplies the nonzero canonical pool before returning the coordinator. SecurityPoolSet and CoordinatorStateCheckpoint setRepEthPrice(price) Configured nonzero SecurityPool only Caller equals the configured pool. Seeds the coordinator's price value, including zero, for inherited child state. RepEthPriceSet and CoordinatorStateCheckpoint","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"sharetoken","heading":"ShareToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores universe-aware ERC-1155 outcome shares and materializes a holder's persistent source entitlement in selected fork branches. Source Read surface: Base and relationship getters are name , symbol , zoltar , canonicalPoolByUniverse , _balances , _supplies , and _operatorApprovals . Standard ERC-1155 reads are supportsInterface , balanceOf , totalSupply , balanceOfBatch , and isApprovedForAll ; protocol-specific reads are isAuthorized , totalSupplyForOutcome , maximumOutcomeSupply , balanceOfOutcome , balanceOfShares , getMigratedShareAmountAttoShares , getTokenId , getTokenIds , and unpackTokenId . Transaction Caller Main prerequisites State or asset effect Primary signals setApprovalForAll(operator, approved) Any token account setting its own operator approval The operator differs from the caller. Sets or clears the operator's authority over all of the caller's outcome-token balances. ApprovalForAll Both safeTransferFrom(...) overloads Share holder or approved ERC-1155 operator Caller holds the source balance or has operator approval; the source account has not materialized that token into any child branch; destination is nonzero; the source balance is sufficient; under A22 asset-recipient compatibility , a contract recipient accepts the ERC-1155 callback. Transfers one outcome-token balance without changing supply. TransferSingle Both safeBatchTransferFrom(...) overloads Share holder or approved ERC-1155 operator for a nonempty batch; any caller for an empty batch ID and value array lengths match. A nonempty batch also requires holder or operator authority, no listed source token that the source account has already materialized into a child branch, a nonzero destination, sufficient source balances, and, under A22 asset-recipient compatibility , an accepting ERC-1155 callback from a contract recipient; the empty-batch no-op performs none of those checks. A nonempty batch transfers each listed outcome-token balance without changing supply. Equal empty ID and value arrays return as a no-op without an event. TransferBatch for a nonempty batch; no event for an empty batch migrate(fromId, targetOutcomeIndexes) Holder of the source token ID Source universe forked; canonical source pool is Operational or PoolForked , and an Operational source has no inherited fixed outcome because auto-fork activation rejects one; positive source balance; nonempty, strictly increasing, well-formed outcomes; every target in a multi-target call already has a canonical child pool; after the branch-creation window, a single target must also already exist; at least one selected child has an unmaterialized balance; under A22 asset-recipient compatibility , a contract holder accepts onERC1155Received for every target mint. If needed, first freezes the operational source pool and records its fork snapshot. A single-target call may lazily create that child while the branch-creation window is open. It keeps and locks the holder's source entitlement, then mints each selected child-universe token ID up to the current source balance. Later source additions materialize only the unminted delta. A contract holder receives the ERC-1155 single-receiver callback for each mint; rejection rolls back the mint and preceding fork or child setup. PoolForkModeActivated , PoolAccountingCheckpoint , SecurityPoolForkSnapshot , ParentRepLocked , and optionally DisputeStakedRepDrainedAtFork when auto-forking; SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , and ChildPoolLinked when lazily deploying, plus DeployChild , ChildRepSplit , PoolHeldRepSweptToChild , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , and ChildDisputeStakedRepMaterialized as applicable; then one ERC-1155 mint TransferSingle and Migrate per materialized target on successful callbacks authorize(securityPoolCandidate) Initially authorized SecurityPoolFactory for an origin pool; an authorized parent SecurityPool for a child pool Caller is already authorized; the candidate reports this exact share token; its universe has no different canonical pool. Establishes the candidate as canonicalPoolByUniverse for its universe and adds it to the set allowed to mint, burn, and authorize descendants. Reauthorizing the same candidate is a no-op. AuthorizationUpdated on first authorization; no event when the same candidate is already authorized mintCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero; amount is positive; under A22 asset-recipient compatibility , a contract account accepts onERC1155BatchReceived . Mints amount each of Invalid, Yes, and No to account , then invokes its ERC-1155 batch-receiver callback when it is a contract. Rejection rolls back the mint and the authorized pool's surrounding transaction. TransferBatch on a successful callback burnCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero and has at least amount of every outcome. Burns amount each of Invalid, Yes, and No from account ; global outcome supplies may differ. TransferBatch burnTokenIdAndGetRemainingSupply(tokenId, account) An authorized SecurityPool account is nonzero; caller is authorized. Burns account 's full balance of tokenId and returns the burned amount and that token ID's remaining supply. TransferSingle , including when the burned balance is zero","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"uniformpricedualcapbatchauction","heading":"UniformPriceDualCapBatchAuction","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Collects ETH bids under ETH-raise and REP-sale caps, computes one clearing result, and supports paged settlement. AVL, cumulative-allocation, and refund-prefix mechanics live in UniformPriceDualCapBatchAuctionStorage , an internal storage library. Source Read surface: Auction summary getters are maxAttoRepBeingSold , attoEthRaiseCap , finalized , clearingTick , ethFilledAtClearingAttoEth , attoEthRaised , totalAttoRepPurchased , auctionStarted , minBidSizeAttoEth , owner , underfunded , underfundedThreshold , underfundedWinningAttoEth , and activeTickCount . pendingEthRefundsAttoEth reports ETH whose gas-bounded push failed during settlement and can still be pulled. Use computeClearing , previewFinalization , tickToPrice , getTickSummary , getTickCount , getTickPage , getActiveTickPage , getBidCountAtTick , getBidPageAtTick , getBidderBidCount , and getBidderBidPage before finalizing or submitting settlement indexes. Transaction Caller Main prerequisites State or asset effect Primary signals startAuction(attoEthRaiseCap, maxAttoRepBeingSold) Auction owner ( SecurityPoolForker ) only Auction not previously started; both caps are positive; the REP cap does not exceed 11 million REP; the ETH cap fits in uint128 ; the block timestamp fits in uint48 . Starts the one-week auction and fixes its two caps and minimum bid. AuctionStarted submitBid(tick) with ETH Any bidder Auction active and unfinalized; before one-week deadline; bid meets minBidSizeAttoEth ; tick maps to nonzero price; the individual bid and the resulting cumulative ETH at that tick each fit in uint128 . Adds ETH demand at the selected positive-price tick while extending that tick's append-only cumulative bid and refund history, including when a fully refunded tick becomes active again. BidSubmitted refundLosingBids(tickIndices) Bidder for its own bids Auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to the caller and are strictly losing and unrefunded. A nonempty list marks the caller's bids already provably below the current clearing tick and attempts an immediate gas-bounded ETH refund. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails refundLosingBidsFor(bidder, tickIndices) Auction owner ( SecurityPoolForker ) only; public callers use settleAuctionBids Named bidder is nonzero; auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to that bidder and are strictly losing and unrefunded. A nonempty list marks and attempts a gas-bounded refund of a named bidder's bids already provably below the current clearing tick. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails finalize() Auction owner ( SecurityPoolForker ) only; users reach it through finalizeTruthAuction Auction started, not finalized, and one-week deadline reached; owner accepts the proceeds ETH call, including zero value. Fixes the clearing mode, clearing tick, ETH totals, and aggregate REP allocation, then calls the owner with the resulting proceeds, including when zero. A rejected call reverts finalization and its event. AuctionFinalized withdrawBids(withdrawFor, tickIndices, proRataTotal, secondaryProRataTotal) Auction owner only Auction finalized; caller is owner. Nonempty indexes belong to withdrawFor and remain unsettled. For a nonempty list, returns refunds, purchased REP, and two companion pro-rata allocations for the selected beneficiary bids so the forker can credit REP backing units, capacity ownership, and bad debt. Withdrawal-time allocation assigns division dust from deterministic cumulative ETH positions, making each payout independent of claim order. A rejected, reverted, or gas-exhausted positive refund push is gas-bounded and deferred rather than reverting or starving the REP, capacity-ownership, and bad-debt settlement. An empty list returns four zeros without changing bids, emitting events, or calling the beneficiary. BidSettled per processed bid; EthRefundDeferred when a positive push fails withdrawPendingEthRefund() Bidder with deferred ETH Caller has a positive pendingEthRefundsAttoEth balance and currently accepts ETH. Clears the caller's complete deferred refund and emits its withdrawal before transferring without the push-refund gas cap, so callback-created deferrals follow the clear in log order. A rejected pull reverts the transfer, clear, and event. PendingEthRefundWithdrawn","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"","heading":"","keywords":["invariants","safety","liveness","properties"],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"An invariant is a statement that must remain true at every successful external-call boundary, across every legal lifecycle transition, or under the economic assumptions named beside it. Reverts may interrupt a transition, but they must roll back all of its state and asset effects. The catalog separates local contract guards from cross-contract properties and economic assumptions. It also records launch-critical properties that the current contracts do not preserve. A documented implementation choice is not automatically a safe invariant. See the Protocol Security Model for assumptions A01–A28. Funded and underfunded clearing are explained in the Truth Auction clearing guide ; oracle notional and residual-loss analysis are explained in OpenOracle Integration . Four connected layers run from local authority guards through cross-contract conservation and lifecycle liveness to economic security. A failure in an earlier layer invalidates the safety claims built above it. Invariant Dependency Correct callers are necessary but insufficient. Their actions must conserve assets, every mandatory state transition must remain reachable, and the named participation and inclusion assumptions must hold where the protocol deliberately relies on economic incentives. Search Type Any type Enforcement status Any status Subsystem Any subsystem Expand visible Collapse all Reset filters Loading invariants… No invariants match the selected filters.","title":"Protocol invariants","topic":"Safety","weight":1},{"fragment":"standing","heading":"Classification and Scope","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Each entry separates the kind of property from its current enforcement status. A status describes the strongest claim supported by the local implementation and tests; it is not a formal proof. Changes to any contract named in the evidence column must preserve the full statement, not only the check performed in one function. Type Meaning Safety A forbidden caller, state, transition, or replay cannot succeed. Conservation Assets, liabilities, supplies, and claims reconcile without duplication or loss. Liveness A required permissionless action remains executable under the stated conditions. External assumption The property depends on participant behavior, inclusion, liquidity, or token behavior outside the contracts. Enforcement status Meaning Required response Contract guard A direct authorization, bound, or state check enforces the property locally. Keep the guard and test its negative boundary. Reviewed preservation Several contracts cooperate to preserve the property in reviewed flows and tests. Protect it with cross-contract invariant and lifecycle tests. Open violation A concrete sequence reaches a state that contradicts the required property. Fix before deployment and retain a regression test. Economic or external assumption Solidity cannot guarantee the property without honest participation, liquidity, or transaction inclusion. State and monitor the assumption, and document both its failure mode and the guarantees deliberately excluded from the protocol model. Proposed guard The property closes a missing safety or verification boundary not currently represented as one assertion. Add a contract check, model assertion, or invariant harness where practical. Invariant identifiers are stable review labels. They are grouped by authority, universe, question, asset, share, vault, fork, escalation, oracle, auction, lifecycle, and observability boundaries rather than by Solidity file.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"standing","heading":"Terms used in the catalog","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Pool question The question whose outcome determines the pool's winning shares and escalation payouts. Fork question The ended question recorded by the universe fork. It may differ from the pool question. Child branch A well-formed answer encoding for the fork question, used to derive one child universe from its parent. SecurityPool's binary pool-question paths specifically use Invalid, Yes, and No. Continuation game A child escalation game initialized from an unresolved parent game's fork snapshot. Fixed payout outcome The pool-question outcome a continuation game must use after its deadline, regardless of its copied balance leader. Direct fork A caller invokes Zoltar's universe-fork entrypoint directly rather than through a pool's own non-decision path. Recursive fork A fork of a child universe created by an earlier fork. Tracked balance An amount recorded by protocol accounting; it excludes unsolicited assets unless a named transition explicitly credits them. Consumed claim A deposit, bid, or proof that has already been settled, refunded, exported, or otherwise made unusable.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"zoltar-invariants","heading":"Zoltar Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern core universe creation, REP supply, universe forks, deterministic child identities, and branch-specific REP migration.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"universes","heading":"Universes, REP Supply, and Migration","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"UNI-01 One fork per universe Required property An initialized universe records at most one fork time and one fork question. Example: After a universe forks on question 17, a second call attempting to fork it on question 22 reverts. Type Safety Enforcement status Contract guard Primary evidence Zoltar.forkUniverse UNI-02 Valid fork questions Required property forkUniverse accepts only a question that exists in ZoltarQuestionData and whose end time has been reached ( block.timestamp >= endTime ). A pool may separately require the fork question to equal its pool question. Zoltar intentionally imposes no question-creation age, universe registration, or pool-relevance condition. Example: At exactly a known question's end time, a caller may fork a universe even when that question came from another universe or was created with an already-past end time; an unknown or still-open question is rejected. Requiring a universe-relevant or sufficiently aged question is not a Zoltar invariant. Type Safety Enforcement status Contract guard Primary evidence forkUniverse , question model Related external boundaries A15 intended question selection UNI-03 Fork threshold burn and credit Required property The fork threshold is ⌊parent theoretical supply / forkThresholdDivisor⌋ . Starting a fork burns that amount of the initiator's parent REP, reduces the parent theoretical supply by the same amount, and credits the threshold minus ⌊threshold / forkBurnDivisor⌋ for migration. The configured forkBurnDivisor is at least 5 , so the uncredited haircut cannot exceed 20%. Example: With a 1,000 REP theoretical supply, threshold divisor 20, and burn divisor 5, the initiator commits 50 parent REP, pays a 10 REP haircut, and receives 40 REP of migration credit. Type Conservation Enforcement status Contract guard Primary evidence getForkThresholdAttoRep and forkUniverse UNI-04 Deterministic child universe IDs Required property A child universe identity is exactly the truncated hash of its parent universe and fork outcome; one parent-outcome pair maps to one child. Example: Repeatedly deriving the Yes child of the same parent returns the same universe ID, while deriving its No child returns a different ID. Type Safety Enforcement status Contract guard Primary evidence getChildUniverseId UNI-05 Well-formed child outcomes Required property Only well-formed outcomes of the recorded fork question can deploy child universes or receive migrated REP. Example: For a binary categorical fork question whose valid indexes are 0, 1, and 2, passing index 3 cannot deploy a child or receive child REP. Type Safety Enforcement status Contract guard Primary evidence deployChild and splitRepInternal UNI-06 Per-child migration mint limit Required property For one migrator and one child, cumulative child REP minted never exceeds that migrator's parent migration balance. Example: A migrator with 40 REP of migration credit cannot mint 25 REP and later mint another 20 REP into the same child. Type Safety Enforcement status Contract guard Primary evidence childMigrationRepAmounts UNI-07 Branch-specific child REP Required property One burned parent migration balance may mint the same credited amount into several selected children. Each minted balance belongs to a different child REP token, and migration never restores the burned parent REP. Example: A 40 REP migration credit may mint 40 Yes-child REP and 40 No-child REP, but neither balance is parent REP and the burned parent balance remains zero. Type Conservation Enforcement status Reviewed preservation Primary evidence splitRepInternal , ReputationToken UNI-08 Child theoretical supply snapshot Required property The child theoretical-supply snapshot equals the parent's theoretical supply after the threshold burn plus the initiator's post-haircut migration credit. Each child therefore excludes the uncredited fork haircut. Example: If a fork removes 50 parent REP and credits 40 migration REP, each child's theoretical snapshot is 10 REP below the pre-fork supply. Type Conservation Enforcement status Reviewed preservation Primary evidence childUniverseTheoreticalSupplySnapshotsAttoRep UNI-09 Child REP supply coherence Required property For each child REP token, total minted supply equals the sum of holder balances, never exceeds the child's theoretical-supply snapshot, and uses the same theoretical maximum recorded by the child universe. Example: If two migrators mint 12 and 7 child REP, the child token supply is 19 REP, both balances sum to 19 REP, and minting does not change the child's theoretical maximum. Type Conservation Enforcement status Reviewed preservation Primary evidence splitMigrationRep and child universe accounting , ReputationToken supply , aggregate child-supply invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"questions","heading":"Questions and Answer Classification","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"QST-01 Question registry and classifier coherence Required property Every successful question creation appends its deterministic ID exactly once and leaves prior question data and labels immutable. For every stored question and answer, the malformed classifier is true exactly when the public answer name is Malformed . Example: Appending a scalar question after two categorical questions preserves the first two records and their order; a scalar answer with reserved bits is both classified and named malformed. Type Safety Enforcement status Reviewed preservation Primary evidence createQuestion , getQuestions , and answer classification , registry and classifier invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"statoblast-invariants","heading":"Augur Statoblast Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern Statoblast deployment, SecurityPool accounting, fork migration, escalation, oracle operations, truth auctions, and the cross-contract lifecycle built on Zoltar universes.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"authority","heading":"Authority and Deployment Identity","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A23 verified deployments and A27 cryptographic identity . AUTH-01 Coordinator and forker-only operations Required property Only the immutable coordinator may execute price-sensitive pool operations, and only the immutable forker may mutate fork accounting or transfer migration assets. Example: A wallet calling withdrawRepFromVault directly is rejected because only the coordinator may call it. Type Safety Enforcement status Contract guard Primary evidence SecurityPool.onlyValidOracle and onlyForker AUTH-02 Atomic pool and coordinator binding Required property The pool and its coordinator bind once during atomic factory deployment; no external caller gets a transaction boundary in which it can install a substitute pool. Example: A caller cannot front-run factory deployment and make the coordinator point to an attacker-controlled pool. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , setSecurityPool AUTH-03 Immutable protocol logic Required property No mutable owner, governance address, upgrade implementation, or emergency operator can replace protocol logic after deployment. Example: After deployment, no administrator can point a pool proxy at different bytecode because the pool has no upgrade slot. Type Safety Enforcement status Reviewed preservation Primary evidence Immutable release posture AUTH-04 Deterministic deployment identity Required property Every canonical CREATE2 address commits to the correct factory, salt, constructor arguments, parent identity, universe, question, and multiplier. Example: Changing a child's question ID changes its derived address instead of deploying different semantics at the expected address. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , addressDerivation.ts AUTH-05 Delegate storage compatibility Required property Delegatecall targets are constructor-installed protocol modules, and every target interprets the forker's storage with the same layout. Example: The vault-migration delegate reads forkDataByPool from the same slot used by the forker rather than corrupting adjacent state. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolForker , interface and storage-layout regression tests AUTH-06 Permissionless settlement beneficiaries Required property Anyone may trigger permissionless settlement, but the caller cannot choose its beneficiary. Each transfer or REP-backing-unit credit goes only to the vault, bidder, depositor, or share holder recorded for that claim. Example: Alice may settle Bob's auction bid, but the purchased REP remains pool-held while the corresponding REP backing units and capacity ownership are credited to Bob's vault. Type Safety Enforcement status Contract guard Primary evidence Contract interaction reference AUTH-07 Published deployment verification Required property For every published deployment address, the runtime-bytecode hash and constructor-installed dependencies must match the reviewed release manifest. Example: A manifest check fails when the published coordinator address contains code built with a different OpenOracle dependency. Type Safety Enforcement status Proposed guard Primary evidence Deployment Status Oracle limits , mainnet manifest , Sepolia manifest AUTH-08 Factory registry and lineage bijection Required property Every factory deployment record names one unique pool whose runtime parent, share token, universe, and question match the record. Its pool-to-origin reverse lookup and origin/universe forward lookup are mutual inverses, and its recorded share token authorizes that pool. Example: Looking up a child pool's origin and then resolving that origin with the child's universe returns the same child, not a pool from an independent origin lineage in that universe. Type Safety Enforcement status Reviewed preservation Primary evidence factory registry and canonical lookups , share-token authorization , stateful registry-bijection test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"pool-accounting","heading":"Pool Assets, Shares, and Vaults","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"The raw ETH balance needs an explicit surplus term. Without it, forced ETH is indistinguishable from collateral and can change an exchange rate or lifecycle precondition. rawEthBalanceAttoEth = settlementCollateralAttoEth + unallocatedAccruedFeesAttoEth + totalClaimableVaultFeesAttoEth + explicitSurplusAttoEth Every successful external call should reconcile raw ETH into named liabilities or an explicit surplus bucket. All equation terms are denominated in attoETH; raw balance alone must not define collateral. BAL-01 ETH conservation Required property At every successful external-call boundary, raw ETH = tracked collateral + unallocated fee reserve + total claimable vault fees + unaccounted surplus . Surplus is the derived remainder and is never a protocol liability. Example: If the pool holds 12 ETH, owes 2 ETH in fees, and tracks 9 ETH of collateral, the remaining 1 ETH is surplus rather than additional collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence Pool ETH accounting , stateful exact-accounting invariant tests BAL-02 Unsolicited ETH isolation Required property Unsolicited ETH cannot change collateral, initialize an exchange rate, increase liabilities, or block a required transition. Example: ETH forced into an empty pool does not let the first complete-set mint use that ETH as its collateral base. Type Conservation Enforcement status Reviewed preservation Primary evidence receive , tracked collateral, and fee redemption , auction finalization BAL-03 Collateral capacity Required property At a complete-set mint boundary, the resulting tracked collateral must fit the current ETH minting capacity derived from total REP-denominated capacity ownership, the live REP-per-ETH price, and the pool security multiplier. Finalized auction ownership enters total ownership, and positive-purchase ownership also enters fee eligibility and accrues fees whether or not a bidder has claimed it. Unassigned auction ownership can provide additional minting headroom only when its explicit position remains healthy after the mint. Existing open interest is not deleted when later repricing lowers live capacity, so this is not a continuous collateral-below-capacity invariant. Fee checkpointing and redemption cannot reclassify unsolicited ETH as collateral, and a price change reprices capacity without iterating through vaults. Example: If current oracle-priced capacity is 10 ETH, a mint that would raise tracked collateral from 9 ETH to 11 ETH reverts even if the raw balance contains surplus ETH. A later REP price change can lower capacity below 9 ETH without changing the existing open interest or ownership records. Type Conservation Enforcement status Reviewed preservation Primary evidence _requireCapacityNotExceeded , setPoolFinancials , and fee redemption , fork finalization BAL-04 REP coverage boundaries Required property A REP withdrawal or escalation deposit must leave the affected vault and aggregate pool totals independently passing both live open-interest health branches. A delegated liquidation receiver must pass both branches after accepting debt; liquidation does not require the unhealthy target to become healthy. Subsequent repricing may make an existing position unhealthy without deleting its open interest, enabling liquidation rather than retroactively reverting the price change. The associated-REP branch may count locally dispute-staked REP; the free-REP branch counts only pool-held vault REP and uses at least the 10,500-BPS liquidation-award reserve. Required backing rounds upward. A delegated receiver's factor of 10,000 is exactly the protocol minimum, and any higher approved factor multiplies both branches at execution. Example: A receiver approved at 12,000 BPS must retain 1.2× the upward-rounded associated-REP requirement and 1.2× the upward-rounded free-REP requirement after accepting live debt. Passing only one branch is insufficient. Type Safety Enforcement status Contract guard Primary evidence Capacity ownership and withdrawal checks BAL-05 REP backing units conversion consistency Required property After deposits, withdrawals, liquidations, escalation transfers, migration, and auction claims, the same current pool-held REP balance and total REP backing units govern both REP-to-backing-unit and backing-units-to-REP conversion. Both proportional directions use full-precision multiplication and division so a representable claim cannot fail only because its intermediate product exceeds uint256 . Example: Depositing REP and immediately converting the resulting REP backing units back to REP uses the same denominator rather than a stale pre-deposit balance. Type Conservation Enforcement status Reviewed preservation Primary evidence attoRepToBackingUnits and backingUnitsToAttoRep BAL-06 Fee liability accounting Required property Every attoETH of accrued fees is represented once as unallocated reserve or claimable vault fees, and redemption clears the claimable fee balance before ETH is sent. Example: Assigning 3 attoETH of reserve to a vault decreases unallocated reserve by 3 and increases that vault's claimable fees by 3, without creating a second claim. Type Conservation Enforcement status Reviewed preservation Primary evidence Fee accumulator and redeemFees BAL-07 Fee reserve protection Required property Fork transfers and redemptions cannot spend ETH reserved for unpaid or unallocated fees. Example: A fork transfer may move 8 ETH of collateral from a 10 ETH balance that also contains 2 ETH of fee liabilities, but may not move all 10 ETH. Type Conservation Enforcement status Contract guard Primary evidence transferEth BAL-08 Aggregate pool-ledger coherence Required property totalClaimableVaultFeesAttoEth equals summed unpaid vault fees. In Operational , fee-eligible capacity ownership equals summed live vault capacity ownership plus sold truth-auction ownership awaiting claim. Uncheckpointed capacity ownership covers fee-eligible ownership not yet reconciled to the current global index, including an unclaimed auction allocation after the index advances. During ForkMigration , configured migrated capacity ownerships remain pending outside those pool aggregates. A PoolForked parent retains its fork-time total and fee-eligible snapshots as individual vaults migrate. After a positive-purchase truth-auction activation, total capacity ownership equals fee-eligible capacity ownership, including the outstanding unclaimed auction allocation in both aggregates. If the auction purchases zero REP, the unmigrated remainder remains explicit unassigned capacity but stays outside fee eligibility. At finalization and after every claim, every capacity unit counted by the child belongs either to a real vault or to that nonwithdrawable unassigned position. The auction settlement specification defines that zero-purchase allocation rule. Example: Migrating one of two vaults that each own 3 REP of capacity leaves the parent's frozen 6 REP total unchanged and records 3 REP as unassigned in the child. With purchased REP, finalization adds those 3 REP of sold ownership to fee eligibility immediately, and later claims only transfer the position to bidder vaults. With zero purchased REP, the same 3 REP remains system-owned and ineligible. Type Conservation Enforcement status Reviewed preservation Primary evidence getPoolAccountingSnapshot and vault checkpointing , truth-auction claim accounting , stateful ledger and claim-order invariant tests SHARE-01 Complete-set symmetry Required property A complete-set mint or burn changes Invalid, Yes, and No balances by the same amount in the same universe. Example: Minting five complete sets creates five Invalid, five Yes, and five No shares for that universe. Type Conservation Enforcement status Contract guard Primary evidence mintCompleteSets and burnCompleteSets SHARE-02 Complete-set redemption Required property Before outcome finalization, burning B complete sets returns ⌊B × tracked collateral / complete-set supply⌋ after the fee checkpoint, then reduces supply by B and collateral by the ETH returned. Example: With 100 sets and 10 ETH of tracked collateral, burning 10 sets returns 1 ETH before integer-rounding residue. Type Conservation Enforcement status Reviewed preservation Primary evidence redeemCompleteSet SHARE-03 Positive-output minting Required property A successful positive-value complete-set mint returns a positive share amount. Example: A 1 attoETH mint that rounds to zero shares reverts instead of accepting the ETH. Type Conservation Enforcement status Contract guard Primary evidence attoEthToAttoShares and createCompleteSet SHARE-04 Child supply denominators Required property Child setup copies the frozen parent's remaining economic claim supply as its collateral denominator. That supply includes both materialized child ERC-1155 balances and source entitlements that can materialize later. Complete-set minting adds claims, while complete-set and winning-share redemption consume them. Example: If a parent freezes with 10 claims per outcome and a child has no materialized shares yet, setup still records 10. A new minter receives only the shares purchased at that established rate, while all 10 source claims retain their reserved collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence attoEthToAttoShares and redeemShares and ShareToken.migrate SHARE-05 Winning-share payout cap Required property Total ETH paid to winning shares never exceeds the collateral available when redemption begins; rounding residue remains for later winning holders. Example: When two winners redeem sequentially, the second receives its fraction of the collateral left after the first rather than a fraction of the original balance paid twice. Type Conservation Enforcement status Contract guard Primary evidence redeemShares SHARE-06 Share supply conservation Required property For every ERC-1155 share ID, total supply equals the sum of holder balances through mint, transfer, and burn operations. Per-child materialization and duplicate prevention are owned by FORK-10 . Example: Moving two Yes shares between wallets leaves Yes supply unchanged; burning one Yes share reduces both the holder-balance sum and Yes supply by one. Type Conservation Enforcement status Reviewed preservation Primary evidence ERC-1155 supply accounting , modeled holder-supply invariant test VAULT-01 Dispute-staked REP isolation Required property REP escrowed in an escalation game cannot also be withdrawn, liquidated as pool-held vault REP backing, or migrated through the ordinary non-escrowed vault path. Example: A vault initially attributed 20 REP that dispute-stakes 5 REP is left with 15 REP of vault backing plus a separate 5 REP claim. It cannot make an ordinary withdrawal while that escrow remains; ordinary vault migration moves only the 15 REP backing, while the dispute-staked REP claim follows its separate migration path. Type Conservation Enforcement status Reviewed preservation Primary evidence Escrow checks , EscalationGame VAULT-02 Liquidation conservation and freshness Required property A liquidation transfers rather than creates aggregate REP backing units and capacity ownership, and total capacity ownership across target and receiver is conserved exactly. A nominal debt quote is bounded by the request, target open interest, and the amount whose complete REP award the target can fund. Proportional ownership is rounded downward; moved debt is the receiver's exact live open-interest increase and is bounded by that quote. A delegated route additionally bounds moved debt by its staged approval reservation; the self-receiving route has no approval reservation. On a full-target request, target-local bad debt is target open interest minus exact moved debt, so it may include an award-unfunded slice and integer-allocation residue. Because vault open interest is independently derived with upward rounding, exact before-and-after vault debt deltas are not a conservation identity. Unmatched ownership, escalation claims, accrued fees, and pool-held REP surplus remain with the target. Execution rejects changed target snapshots, enforces the receiver's live post-transfer health factor, and every delegated terminal path consumes or releases its reservation exactly once. Example: If the target or receiver changes after staging, execution may fail safely and release the full reservation. The operator receives no ownership merely for submitting the transaction. Type Conservation Enforcement status Reviewed preservation Primary evidence Liquidation design , performLiquidation , executeStagedOperation VAULT-04 Escalation claims are non-transferable and migration-neutral Required property An escalation claim is permanently bound to the depositor committed in its carry leaf and has no transfer path or parent-OI migration power. Liquidation cannot read, move, or acquire it. Final settlement pays the committed depositor after proof verification and replay protection. Example: Moving half a vault's capacity ownership leaves every escalation claim unchanged, and locking that claim cannot increase the OI routed to any child. Type Safety Enforcement status Reviewed preservation Primary evidence withdrawDeposit , liquidation design VAULT-03 Vault registry coherence Required property The vault registry is append-only and contains each nonzero address at most once. Registration does not require economic state: any vault path that calls _registerVault , including a public fee checkpoint for an empty address, can append it. Pagination is newest-registered first; later activity and full exit do not reorder or remove an entry. Consumers read current vault and escalation state to decide which registered vaults to display. Example: After a vault has fully exited and its REP backing, capacity ownership, claimable fees, escalation stake, bad debt, and open interest are all zero, its address remains in registry pagination. A UI can filter the empty position without losing historical discovery. Type Safety Enforcement status Reviewed preservation Primary evidence _registerVault , registry pagination and direct-claim boundary tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"forks","heading":"Forks and Child Isolation","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"FORK-01 Parent pool finality Required property Once the parent universe forks, parent operational flows freeze and the parent pool never re-enters Operational . Example: After fork activation, the parent cannot accept another REP deposit or later return from PoolForked to Operational . Type Safety Enforcement status Contract guard Primary evidence isOperational FORK-02 Immutable fork snapshot Required property When the parent first enters fork mode, it captures fork time, collateral, REP buckets, total REP backing units, escalation state, and capacity ownership exactly once. Those immutable values are the common basis for every child of that fork. Example: REP sent unsolicited to the parent after fork mode begins cannot change the REP snapshot used to initialize a later-created No child. Type Safety Enforcement status Reviewed preservation Primary evidence Fork snapshot preparation FORK-03 Isolated migration proxy Required property Each parent pool maps to exactly one deterministic migration-proxy address, and that address is the pool's isolated identity in the Zoltar migration ledger. Example: Two parent pools using the same forker derive different proxy addresses and therefore cannot spend each other's migration credit. Type Safety Enforcement status Reviewed preservation Primary evidence getMigrationProxyAddress FORK-04 Prefunded proxy isolation Required property Preexisting or unsolicited REP at the deterministic proxy cannot block initiation or become unclassified migration principal. Example: An attacker sending 1 REP to the future proxy address cannot make fork initiation revert or count that REP as pool migration principal. Type Liveness Enforcement status Reviewed preservation Primary evidence initiateSecurityPoolFork FORK-05 Eight-week migration boundary Required property Statoblast child-pool creation, pool-local vault migration, pool-proxy REP splitting, and own-fork claims are allowed through forkActivationTime + 8 weeks , inclusive, and are closed after that timestamp. forkActivationTime is recorded when the parent pool enters PoolForked ; post-migration activation begins only after its window ends. Share materialization has no expiry, but after the deadline it can target only an already-created child. This pool-local boundary does not limit Zoltar.deployChild , addRepToMigrationBalance , or splitMigrationRep ; those Zoltar operations follow their own fork identity and balance guards without this timestamp check. Example: Vault migration succeeds exactly at the eight-week deadline and reverts one second later. Type Safety Enforcement status Reviewed preservation Primary evidence forkActivationTime recording and child guards , MIGRATION_TIME , deployChild , addRepToMigrationBalance , and splitMigrationRep , migration deadline tests FORK-06 Child deployment identity Required property A deployed child must name the requesting pool as its parent and must use the child universe derived from that parent's selected branch. Its factory, forker, auction, share token, pool question, and security multiplier must equal the values supplied or inherited by the parent factory path. External-universe fork initiation requires the source pool to be authorized by its declared share token; own-game initiation does not. That relationship is not proof of configured-factory registration. The auction must have deployed code and must not already be trusted for another child. Example: On the external-universe path, the forker first requires the source to be authorized by its declared share token without treating that check as factory registration. The configured canonical factory installs the parent's share token, pool question, and multiplier in the child; the post-deployment guard then checks deployed and unique auction assignment plus parent, universe, factory, forker, and auction relationships before linking it. Type Safety Enforcement status Reviewed preservation Primary evidence _prepareForkState external-fork authorization guard , _validateChildPoolDeployment and deployChildSecurityPool FORK-07 Single-use non-escrowed vault migration Required property Vault migration converts the parent's backing-unit claim to REP, credits that REP amount as child-local backing units, and clears the corresponding parent REP backing units and capacity ownership before reuse. A positive parent claim that maps to zero child REP receives zero child backing units, so rounding cannot preserve a stale claim that later regains value. Claimable fees remain in the parent vault while proportional settlement collateral routes separately at pool level. Example: After Alice migrates her non-escrowed vault accounting to the Yes child, a second migration call cannot credit the same parent REP backing units to the No child. Type Conservation Enforcement status Reviewed preservation Primary evidence _migrateNonEscrowedVaultAccounting FORK-08 One-time aggregate continuation backing Required property Each selected child initializes the canonical carry snapshot and receives the complete aggregate continuation backing at most once. Optional vault cleanup only clears unresolved parent escalation-deposit accounting; it creates no child escrow and cannot change carried-proof eligibility. The continuation cannot resume until its game balance covers all effective unrelated-fork principal. For an own fork, initial backing must cover sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , the rounded-up 80% minimum, and the live balance must cover the unexported remainder after valid direct pre-resume claims. sourcePrincipalAtForkAttoRep is the raw aggregate stored by the fork snapshot before effective direct-claim deductions. Example: Creating the Yes child installs its snapshot and aggregate backing before Alice acts. Alice may later clear her unresolved parent escalation-deposit accounting, but that cleanup neither funds the child nor enables or disables her winning proof. Type Conservation Enforcement status Contract guard Primary evidence _ensureChildEscalationBacking , resumeFromFork , isForkCarryFundingComplete , _exportForkedEscrowByOutcome , and fork-backing regression tests FORK-09 Cumulative child collateral Required property Cumulative parent ETH transferred to children never exceeds the fork collateral snapshot and is derived from cumulative migrated REP, not per-call rounding. Example: If several calls collectively migrate R child REP, their collateral target is ⌈fork collateral × R / denominator⌉ , independent of how that same R is partitioned. The denominator is vaultRepAtForkAttoRep for an own fork and auctionableAttoRepAtFork otherwise. Type Conservation Enforcement status Contract guard Primary evidence _transferForkMigratedCollateralToChild FORK-10 Share migration integrity Required property Share migration preserves the holder's parent token-ID balance as a persistent entitlement, rejects duplicate or malformed target outcomes, and mints only the unmaterialized balance into each selected child. A used source balance cannot transfer. Example: Materializing seven parent Yes shares in one child leaves all seven parent entitlements visible and locked. A later call can mint seven into another existing child, but cannot mint the first child twice. Type Conservation Enforcement status Contract guard Primary evidence ShareToken.migrate FORK-11 Unequal child share supplies Required property Child pricing uses the fork-time economic claim supply rather than currently materialized outcome supplies. Unequal ERC-1155 supplies therefore do not block complete-set minting or proportional complete-set redemption. The denominator lifecycle and payout mechanics are owned by SHARE-04 . Example: If the economic supply is 12 while materialized supplies are 10, 8, and 6, a new complete set is priced against 12 and adds the same newly purchased balance to all three materialized supplies. Type Conservation Enforcement status Reviewed preservation Primary evidence setTotalSharesAttoShares during auction preparation , attoSharesToAttoEth , redeemCompleteSet , and redeemShares , mintCompleteSets and persistent migration accounting FORK-12 Child activation after settlement Required property Value-free truth-auction settlement activates a child with its tracked migrated collateral plus retained auction ETH, which cannot exceed the parent fork snapshot. Fork-time economic claim accounting keeps later minting and proportional burns independent from materialization order. Example: A child with 9 ETH against a 10 ETH fork snapshot activates after value-free finalization with 9 ETH of tracked collateral; its later operations remain subject to the ordinary share-supply and minting guards. Type Safety Enforcement status Reviewed preservation Primary evidence Truth-auction start and finalization","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"escalation","heading":"Escalation Games and Carried Claims","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"ESC-01 Total escrow reconciliation Required property totalDisputeStakedAttoRep equals effective live vault-denominated escrow plus effective aggregate continuation backing in forkCarryDisputeStakedAttoRep . Continuation backing is not assigned to child-vault health or migration power, but it must enter the aggregate before the truth auction so physically held REP cannot escape the repair haircut. Auction retention floors the aggregate total and the continuation bucket independently; do not reconstruct either aggregate by summing independently rounded vault views. Example: If vault-denominated escrow contributes 4 and 6 REP and an external-fork continuation carries 12 REP of aggregate proof backing, the total is 22 REP, represented as 22 × 10^18 attoREP in totalDisputeStakedAttoRep , before the auction. A 75% retention records ⌊22 × 75%⌋ = 16 total effective REP and ⌊12 × 75%⌋ = 9 aggregate continuation REP. This makes the carried claims absorb the same repair loss without pretending that their backing belongs to a particular child vault. Type Conservation Enforcement status Reviewed preservation Primary evidence Continuation initialization , truth-auction retention accounting ESC-02 Local unresolved REP reconciliation Required property totalLocalUnresolvedAttoRep equals the sum of unresolved local deposits, and each vault-local counter is its exact component. Example: If Alice has 3 unresolved REP and Bob has 5, their counters are 3 and 5 and the global local-unresolved total is 8. Type Conservation Enforcement status Reviewed preservation Primary evidence Unresolved counters ESC-03 Outcome carry reconciliation Required property For each outcome, currentCarryTotalAttoRep equals effective inherited unresolved principal plus unresolved local deposits. Effective inherited principal subtracts immediate-parent direct claims and becomes zero for an inherited losing outcome after finalization. Example: A child snapshots 7 Yes REP, the parent directly claims 2 REP, and the child receives a new unresolved 2 REP Yes deposit. Its current Yes carry is (7 - 2) + 2 = 7 REP . Type Conservation Enforcement status Reviewed preservation Primary evidence Escalation accounting invariants ESC-04 Stable deposit identifiers Required property An ordinary game's stable deposit identifier is its local deposit index. A continuation game derives the identifier from its own address, outcome, and local index, so two different continuation deposits cannot share a carry or nullifier key merely because their local indexes match. Example: Deposit zero on Yes in a child game and deposit zero on Yes in its grandchild produce different stable identifiers. Type Safety Enforcement status Reviewed preservation Primary evidence Stable deposit identity ESC-05 Single-use claims per branch Required property Along any single root-to-descendant fork path, each local or inherited deposit may be claimed, settled, refunded, or exported at most once. Child nullifiers are branch-local, but a direct ancestor claim invalidates the matching proof in every descendant and reduces their effective inherited principal. Otherwise, selected sibling branches maintain separate claim authorization. Example: Consuming a carried proof in a Yes grandchild prevents replay farther down that branch while a sibling remains independent. If the deposit was instead claimed directly from their ancestor, both descendants reject it. Type Safety Enforcement status Reviewed preservation Primary evidence Carry nullifiers , MMR proofs ESC-06 Aggregate export accounting Required property Aggregate unresolved export clears the exporting vault's three outcome totals, unresolved counter, and parent escrow exactly once. It retains the deposit rows and carry commitment solely as immutable proof material for selected children. Example: After Alice exports her aggregate claim, her parent escrow is zero even though her historical deposit row remains available to prove a child claim. Type Conservation Enforcement status Reviewed preservation Primary evidence exportVaultUnresolvedTotalsWithoutTransfer ESC-07 Authenticated carried winner payout Required property A carried claim must authenticate an unconsumed proof for the final winning outcome. The proof fixes the original claim identity and its committed depositor, who receives the normal winning payout even when another account relays the transaction. Liquidation cannot change that recipient. Example: Bob may relay Alice's valid winning proof, but the payout still goes entirely to Alice as the committed depositor. A losing proof cannot consume backing, and reward math may make a winning payout greater than its principal. Type Conservation Enforcement status Contract guard Primary evidence withdrawDeposit ESC-08 Game REP settlement conservation Required property Every REP unit held for a game is accounted for exactly once as winning principal, reward funding, losing principal, unsettled residual, ordinary-game REP swept back to the pool, or fork-continuation REP burned. Settlement cannot create an additional REP claim. Example: Paying winners and sweeping the final residual reduces the game balance to zero without total payouts exceeding the REP previously held. Type Conservation Enforcement status Reviewed preservation Primary evidence Settlement ESC-09 Terminal game outcomes Required property Once resolution or non-decision closes a game path, later deposits cannot reopen or change that terminal result. Example: After Yes becomes final, a later attempt to deposit on No reverts rather than changing the winner. Type Safety Enforcement status Contract guard Primary evidence EscalationGame ESC-10 Ancestor claim replay protection Required property If a deposit is claimed directly from an ancestor during an own fork, every existing or later descendant treats the corresponding proof as spent even if that descendant has not written its own nullifier. Example: Claiming deposit 4 from the parent blocks proof 4 in an already-created child and in a grandchild created later. Type Safety Enforcement status Reviewed preservation Primary evidence Forked claims ESC-11 Residual sweep preconditions Required property Residual REP cannot be settled until effective unresolved principal is zero for every outcome. For a continuation, the sweep first retires its inherited aggregate forkCarryDisputeStakedAttoRep ; after that retirement, no local dispute stake or vault escrow may remain in totalDisputeStakedAttoRep . Inherited losing principal retires at finalization without a proof; winning inherited proofs and every local unresolved deposit still require their applicable terminal transition. Ordinary-game residual returns to the pool, while fork-continuation residual is burned and cannot accrue to child-local backing units. Example: After Yes finalizes, an unclaimed inherited No leaf does not block sweeping. An unclaimed inherited Yes proof, an unsettled local deposit, or a live vault escrow record still does. Type Conservation Enforcement status Contract guard Primary evidence sweepResidualRepToSecurityPool ESC-12 Pool and continuation payout agreement Required property When a universe forks on a pool's question, each child pool and its continuation game use that child's Invalid, Yes, or No branch as the final pool-question outcome. Once a pool inherits that fixed outcome, new local escalation deposits and every later fork transition revert, whether the new universe fork uses the same question or another one. Deposit withdrawal reverts unless the pool and game report the same outcome. Example: A Yes child created by a fork on the pool question pays carried Yes deposits even if copied game balances favored No. It rejects new local dispute-staked REP before escrow, so a later local non-decision cannot lock vault redemption. A later matching or unrelated universe fork leaves the stored SystemState.Operational and fixed Yes outcome unchanged, and eligible share, vault REP, and carried-proof redemption paths remain available, while universe-fork guards still freeze normal operating calls. Type Safety Enforcement status Reviewed preservation Primary evidence activateForkMode , getQuestionResolution , fixed-outcome propagation , and pool/game payout check ESC-13 Non-decision threshold and live start-bond arithmetic Required property For a positive fork threshold F , non-decision requires ⌈F / 2⌉ REP on two outcomes. Therefore the two threshold balances total at least F , while one attoREP less on both outcomes totals strictly less than F . At pool construction, the configured start bond is max(1 REP, theoretical REP supply / 10,000,000) , and a new origin requires the live non-decision threshold to exceed it. If an existing pool's live threshold later falls to or below that configured bond, ordinary game deployment clamps the live start bond to nonDecisionThresholdAttoRep - 1 , provided the threshold exceeds one attoREP. Example: If F = 5 , non-decision requires 3 REP on two outcomes, so the two balances hold 6 REP; balances of 2 and 2 remain below the 5 REP fork threshold. Type Safety Enforcement status Contract guard Primary evidence getNonDecisionThresholdAttoRep , origin threshold admission , deployEscalationGame , and start-liveness regression tests ESC-14 Carry commitment structural integrity Required property For every outcome, exported carry peaks, leaf count, unresolved total, and nullifier root equal the corresponding outcome state. Only leaf-count-selected peak heights are occupied, independently bagging those peaks yields the exported root, and consumed proof indexes are unique. The accounting definition of unresolved total is owned by ESC-03 . Example: Three Yes leaves occupy the height-zero and height-one peaks; bagging those two peaks reproduces the exported Yes root before and after one inherited proof is consumed. Type Conservation Enforcement status Reviewed preservation Primary evidence getForkCarrySnapshot and getForkCarryRoots , bagCarryPeaks , independent carry-structure invariant tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"oracle","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . Accepted oracle design properties—not security findings. Statoblast uses profitable correction, not operation-value collateralization. Report liquidity is intentionally not required to equal or bound the value of withdrawals, liquidations, capacity ownership changes, pool assets, or cumulative operations that use an accepted price. The protocol also makes no bounded-liveness claim for a pending sponsor lane. Every valid dispute restarts settlement, incurs transaction execution, and must fund the contract-specified replacement position. Under ordinary non-dust parameters, protocol fees and the required position also accumulate; before the escalation halt, integer flooring can leave dust-sized rounds unchanged. A participant willing and able to keep submitting and funding disputes may therefore delay unrelated users without a coordinator-level deadline. This paid, capital-unbounded pause is intentional and is not a violation of a Zoltar liveness invariant. Security review should verify the correction-profit calculation, report accounting, dispute access, and the stated inclusion assumption. A concrete bypass of the required position or fee, capital reuse, invalid deadline transition, or cache-conformance failure remains a finding. The mere absence of a report-notional bound or absolute pending-report duration does not. ORA-01 Authorized settlement callbacks Required property Only the configured OpenOracle may supply a settlement callback, and its report identifier must equal the coordinator's pending report. Example: A callback from another contract or for an older report ID cannot update the cached REP/ETH price. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback ORA-02 REP-per-ETH price direction Required property An accepted price is positive REP per ETH: amount2 REP × 1e18 / amount1 WETH . Higher values require more REP for the same ETH obligation. Example: A report of 200 REP for 2 WETH stores 100 REP per ETH, not 0.01 ETH per REP. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback , ratio direction ORA-03 Five-minute price validity Required property A cached price is usable only while nonzero and strictly younger than the five-minute validity window. Example: A price settled exactly five minutes ago is stale; one settled four minutes and 59 seconds ago remains usable. Type Safety Enforcement status Contract guard Primary evidence isPriceValid ORA-04 Single-use staged operations Required property Every staged operation has one immutable identifier. Once execution is attempted with a usable price, success or a caught failure consumes that identifier, emits the result, and prevents a retry. An uncaught transaction revert instead rolls back the entire attempt. Example: A pool-level withdrawal rejection is caught and emitted as failed, after which the same operation ID cannot be executed again. Type Safety Enforcement status Contract guard Primary evidence executeStagedOperation ORA-05 Staged operation targets Required property Withdrawals target the initiating vault. Liquidations name separate operator, receiver, and target roles; the receiver must differ from the target, while the operator may equal either. A liquidation fails when the protected target REP-backing-unit or capacity-ownership snapshot becomes stale. Example: Alice can stage her own REP withdrawal. A liquidation operator can target Bob, but its receiver cannot be Bob; execution fails if Bob changes his REP backing units or capacity ownership after staging. Type Safety Enforcement status Contract guard Primary evidence Staging and snapshot checks ORA-06 Consumed execution failures Required property An expired operation, a liquidation with a stale target snapshot, a withdrawal that would move zero REP, or a liquidation inside the configured minimum price distance is consumed with an observable failure result rather than remaining executable forever. Example: If an earlier withdrawal empties a vault, its second queued withdrawal is consumed as a zero-effect failure rather than retried after every price update. Type Liveness Enforcement status Contract guard Primary evidence executeStagedOperation ORA-07 Staged-operation queue and index bijection Required property Operation IDs are append-only. Active enumeration contains each and only each operation with a live initiator; the bounded pending settlement queue contains unique active IDs; and pendingOperationSlotId is zero for an empty queue or equals its head. Example: Settling four queued operations removes those four IDs from both indexes, leaves an overflow operation active for manual execution, and does not reuse any consumed ID. Type Safety Enforcement status Reviewed preservation Primary evidence active and pending staged-operation indexes , queue/index invariant test ORA-08 Dispute and settlement boundary Required property The settlement deadline is exclusive: disputes require the current clock to be strictly before the deadline, while settlement is available at and after it. Example: At timestamp deadline , a new dispute is rejected and settlement is allowed. Type Safety Enforcement status Contract guard Primary evidence settle and dispute validation ORA-09 Oracle balance-domain boundary Required property Direct REP or WETH held by the coordinator is outside OpenOracle and remains untouched by settlement or recovery. OpenOracle credits balances by beneficiary rather than by report: the callback sends the coordinator's entire withdrawable internal REP and WETH credit to the recorded pending sponsor, including any third-party deposit that names the coordinator as beneficiary. Example: REP transferred directly to the coordinator remains there, while REP deposited into OpenOracle for the coordinator is swept with the report proceeds to the current sponsor and leaves only OpenOracle's balance sentinel. Type Conservation Enforcement status Reviewed preservation Primary evidence pendingReportSponsor and _withdrawOpenOracleReporterBalances , deposit and withdrawTo , coordinator/OpenOracle balance-domain test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"auctions","heading":"Truth Auctions","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"AUC-01 Bid admission Required property Bids are accepted only during the one-week window, at a supported tick whose computed ETH-per-REP price is positive, and at or above the auction's minimum bid size. Example: A bid submitted one second after the one-week window or below the minimum bid size reverts without being recorded. Type Safety Enforcement status Contract guard Primary evidence submitBid AUC-02 Auction caps Required property Final retained ETH never exceeds the ETH raise cap, and total purchased REP never exceeds the REP sale cap. Example: If bids offer 12 ETH against a 10 ETH cap, finalization retains at most 10 ETH and refunds the rest. Type Conservation Enforcement status Contract guard Primary evidence computeClearing and finalize AUC-03 Single-use bid settlement Required property Each bid is settled or refunded at most once; claimed state is set before an external ETH refund. The best-effort push is gas-bounded, and any rejected, reverted, or gas-exhausted positive refund is moved to bidder-specific pull escrow without reverting the bid's REP or capacity ownership settlement. Example: A bidder whose refund callback reenters, rejects ETH, or consumes its complete callback budget cannot withdraw the same bid a second time or starve the remaining settlement because the bid is already marked claimed and the push is gas-bounded; deferred ETH remains available through withdrawPendingEthRefund . Type Safety Enforcement status Contract guard Primary evidence withdrawBids and refund functions AUC-04 Claim-order independence Required property Cumulative allocation math makes per-bid REP, refund, capacity ownership, and bad-debt results independent of post-finalization claim order. Example: Alice claiming before Bob gives each vault the same REP, ETH refund, and capacity ownership as Bob claiming before Alice. Type Conservation Enforcement status Reviewed preservation Primary evidence allocateFromCumulativePosition AUC-05 Allocation and refund reconciliation Required property A zero-demand auction records zero purchased REP. Otherwise, the sum of all winning-bid REP allocations equals the stored aggregate purchase, and each bid's unretained ETH remains refundable. Example: If finalization stores 30 purchased REP, all winning claims together receive exactly 30 REP and every unretained attoETH is refunded. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction settlement AUC-06 Bidder-vault credits Required property A bid claim keeps purchased REP pool-held and transfers the bid's proportional REP backing units, capacity ownership, bad debt, and finalization-to-claim fees from the unassigned position to the bid's recorded vault. Each decrease in unassigned backing, capacity, and bad debt equals the corresponding vault credit. The auction rate is derived only from bounded child-local units, and cumulative credit cannot exceed the finalized denominator. These credits add to rather than replace that vault's migrated position, while capacity-ownership and bad-debt division dust follows each bid's fixed cumulative ETH position. This makes each winner's allocation independent of claim order. A winning dust bid still receives a positive fixed-position capacity ownership share when its REP allocation rounds to zero. Claiming does not change total capacity ownership, fee-eligible capacity ownership, active open interest, total bad debt, retention rate, or aggregate accrued fees. Example: A vault with migrated REP backing units keeps them when settling a winning auction bid. The claim moves the bid's backing units, deterministic capacity share, proportional bad debt, and accrued fees into that vault while leaving every pool aggregate unchanged. If a dust bid's REP share is zero but its capacity ownership share is positive, claiming that bid alone still credits the capacity ownership. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction-claim settlement AUC-07 Uniform weak-demand allocation Required property In underfunded clearing, qualifying ETH is retained bid ETH offered at or above the cap-implied qualification threshold. The threshold is not an execution-price floor. Qualifying bidders collectively receive maxAttoRepBeingSold in proportion to that ETH at the aggregate effective price underfundedWinningAttoEth / maxAttoRepBeingSold . Per-bid cumulative floors then assign indivisible attoETH and attoREP, so a dust winner can round to zero REP without changing the aggregate price. With no qualifying ETH, every bid refunds. Example: If Alice supplies 60% and Bob 40% of qualifying ETH, they receive 60% and 40% of the complete REP sale cap subject to deterministic attoREP rounding. Type Conservation Enforcement status Reviewed preservation Primary evidence Truth Auction clearing design AUC-08 Forced-ETH-resistant finalization Required property Auction finalization succeeds regardless of unsolicited ETH already present in the child pool or forker. Example: One attoETH forced into the child before finalization remains surplus and does not change protocol-accounted collateral. Type Liveness Enforcement status Reviewed preservation Primary evidence finalizeTruthAuctionRepair AUC-09 Bounded bid settlement Required property After the auction deadline, value-free finalization succeeds using migration-routed collateral plus retained auction ETH. Qualifying bids settle for REP, non-qualifying bids refund, and nonzero finalizer ETH reverts. Example: For a 10 ETH target with 6 ETH migrated and 3 ETH retained from bids, the child activates with 9 ETH of accounted collateral and every bid can then settle. Type Liveness Enforcement status Contract guard Primary evidence Weak-demand behavior , finalizeTruthAuctionRepair AUC-10 Persistent tick bid history Required property Bid cumulative ETH remains append-only for a tick even when the active AVL node is deleted and later recreated. Refunded history is subtracted exactly once, so every later accepted bid retains a valid allocation or refund path. Example: Deleting an empty price tick and later bidding at that price continues cumulative ETH after the old history instead of making the new bid unclaimable. Type Conservation Enforcement status Reviewed preservation Primary evidence _appendBid and refund-prefix accounting and repeated tick deletion/recreation tests AUC-11 Auction tree and public-model equivalence Required property Before finalization, active tick pages contain exactly the nonzero ticks not removed by pre-finalization refunds, in descending order, and clearing computed from the AVL tree equals an independent calculation over those bids. Finalization freezes that tree and clearing result: later claims change bid flags and ETH liabilities but not active tick pages. Post-finalization liabilities are owned by AUC-12 . Historical insertion and refund-prefix obligations are owned by AUC-10 . Example: Before finalization, an independent descending-tick scan returns the same clearing result as the contract. After a winner claims, its bid is marked claimed and its refund liability leaves, while the final clearing page and result remain unchanged. Type Safety Enforcement status Reviewed preservation Primary evidence AVL tree and clearing , enumeration and auction integration , seeded independent-model and frozen-snapshot tests AUC-12 Auction ETH liability conservation Required property Before finalization, auction ETH equals active unrefunded bids plus aggregate pendingEthRefundsAttoEth and explicit surplus. After finalization, the remaining balance equals refunds still attached to unclaimed bids plus deferred pendingEthRefundsAttoEth and explicit surplus. Per-bid settlement partitions and aggregate REP allocation are owned by AUC-05 . Example: If a rejected pre-finalization push removes a 2 ETH losing bid from active clearing, raw ETH still equals the remaining active bids plus the 2 ETH deferred refund. After finalization, the same deferred bucket composes with unclaimed-bid refunds and any forced surplus. Type Conservation Enforcement status Reviewed preservation Primary evidence finalize , withdrawBids , and deferred refunds , seeded and deferred-refund ETH-liability tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"lifecycle","heading":"Lifecycle, Liveness, Observability, and External Calls","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A02 viable operating costs , A16 timely inclusion , A06 lifecycle executors , and A26 Ethereum execution . LIFE-01 Pool state transitions Required property Pool state topology permits only Operational → PoolForked for a parent and ForkMigration → ForkTruthAuction → Operational for a child. Example: A child cannot skip directly from ForkMigration to Operational while repair auction work remains required. Type Safety Enforcement status Reviewed preservation Primary evidence SystemState , fork transitions ; see FORK-12 for child accounting consistency. LIFE-02 Permissionless transition liveness Required property Once a resolution, migration, auction-completion, or withdrawal function's documented semantic preconditions hold, dust, unsolicited balances, empty collections, or earlier permissionless calls cannot permanently prevent some caller from completing it. Example: Forced ETH and an empty losing-bid list do not prevent a caller from finalizing an otherwise complete truth auction. Type Liveness Enforcement status Proposed guard Primary evidence FORK-04 , BAL-02 , and AUC-08 LIFE-03 Atomic failure behavior Required property An uncaught revert rolls back every state mutation and transfer in the transition; deliberate consumed failures emit an explicit result and cannot be retried. Example: If a child migration reverts after an attempted token transfer, neither the transfer nor its accounting update remains committed. Type Safety Enforcement status Reviewed preservation Primary evidence Solidity atomicity, coordinator consumed-failure paths LIFE-04 Deadline equality rules Required property Every deadline assigns equality to one documented side of the boundary: migration remains open at equality; auction bidding and public pool finalization are both closed at equality, and finalization opens immediately afterward; oracle disputes are closed and settlement is open at equality; staged operations expire only after equality; and an escalation result is final only after its end timestamp. Example: Exactly at an auction deadline, both a bid and public pool finalization revert; one second later, finalization succeeds. Type Safety Enforcement status Proposed guard Primary evidence Boundary tests across fork migration, auctions, escalation, and OpenOracle LIFE-05 Bounded transition work Required property No mandatory transition loops over an attacker-unbounded history in one transaction; callers use bounded pages, fixed outcome sets, proofs, or balanced-tree traversal. Example: Settling one carried deposit verifies its bounded proof instead of iterating over every deposit ever made in its ancestors. Type Liveness Enforcement status Reviewed preservation Primary evidence Vault pages, bid pages, MMR proofs, and auction AVL traversal OBS-01 Event-state replay equivalence Required property Ordered canonical events reconstruct the same universe, pool, vault, coordinator, and escalation carry state exposed by storage. Replay isolates contract-local counters and recognized emitters, and every replay-supported state transition emits the checkpoint or delta needed to reach its resulting storage value. Example: Replaying seeded pool and vault operations matches their accounting storage, while replaying a deployed parent deposit, fork snapshot, and child carry checkpoint matches the child's roots, peaks, leaf counts, unresolved totals, and nullifier roots. Type Safety Enforcement status Reviewed preservation Primary evidence contract interaction reference , event replay model , actual-log storage-equivalence tests EXT-01 Genesis token behavior boundary Required property Genesis REP and configured WETH obey the exact transfer, return-value, decimal, and callback behavior assumed by pool and oracle accounting. Mainnet relies on external token behavior; Sepolia relies on verified deployments of the reviewed GenesisReputationToken and WETH9 contracts for issuance and transfer mechanics. Burn-sink inaccessibility remains an external assumption on both networks. Example: Genesis REP transfer returns and moves exactly the requested attoREP rather than charging a fee that leaves pool accounting overstated; child REP supply remains bounded by its contract-enforced theoretical ceiling. Type External assumption (mainnet); mixed safety and external assumption (Sepolia) Enforcement status External on mainnet; reviewed issuance and transfer guards under A23 and A26 on Sepolia, with burn-sink inaccessibility still assumed Primary evidence SafeERC20Ops , GenesisReputationToken , WETH9 , the network deployment manifests, and security assumption A21 Related enforced property Canonical child REP behavior is enforced by ReputationToken and its Zoltar-only mint and burn authority. EXT-02 Callback-safe accounting Required property External ETH or token callbacks observe effects already committed for that action. A required delivery that fails reverts without leaving partial accounting; an explicitly deferrable delivery instead commits the action together with an exactly accounted recipient liability. Example: A fee recipient's fallback observes its claimable fees already cleared, so reentry cannot redeem the same claimable fees twice. Type Safety Enforcement status Reviewed preservation Primary evidence Pool redemption, auction refund and deferred-credit, and oracle refund paths Related external liveness boundary A22 participant-controlled recipient compatibility EXT-03 ERC-1155 callback safety Required property ERC-1155 receiver callbacks cannot mint beyond pool capacity, alter the amount minted, or reenter the same accounting effect twice. Example: A receiver callback during complete-set minting cannot call back into the pool and reuse the same ETH capacity for a second mint. Type Safety Enforcement status Reviewed preservation Primary evidence createCompleteSet , ERC-1155 receiver checks, and receiver callback tests Related external liveness boundary A22 participant-controlled recipient compatibility EXT-04 Same-block deadline ordering Required property At a deadline timestamp, mutually exclusive before-deadline and after-deadline actions cannot both succeed. Explicit comparison operators assign equality to one phase, and transaction ordering within the block cannot change that assignment. Example: At the OpenOracle settlement deadline, ordering a dispute before settlement in the block still cannot make the dispute valid. Type Safety Enforcement status Proposed guard Primary evidence ORA-08 and same-block auction ordering analysis EXT-05 Recursive fork gas bound Required property Origin registration, inherited-fork detection during child construction, and direct-claim replay checks must not traverse universe or pool ancestry. Example: At lineage depths 1 and 32, a replay query for the same global deposit id performs the same lineage-registry and claimed-id lookups rather than 1 or 32 parent calls. Type Liveness Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory.getOriginId and getPoolId , cached inherited-fork registry tests, SecurityPoolForker.getEscalationDepositId , and depth-independent replay gas tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["assumptions","security","threats","guarantees"],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Statoblast and Zoltar enforce accounting, authorization, and lifecycle rules onchain. Their economic safety and practical liveness still depend on the participant, market, asset, deployment, client, data, Ethereum, and cryptographic assumptions below. An assumption is not a guarantee enforced by the contracts. Each entry says what must remain true, what can fail if it does not, and where the related mechanics are explained. The Protocol Invariants catalog lists contract-enforced properties and their implementation evidence. Participation Forks and markets Oracle and liveness Technical foundation Excluded guarantees","title":"Security model","topic":"Safety","weight":1},{"fragment":"orientation","heading":"Security-model orientation","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"This catalogue protects REP, ETH settlement collateral, outcome shares, vault backing, escalation deposits, child-universe accounting, and the integrity of event-derived state. Boundary Examples Trusted protocol components Zoltar, security pools, share tokens, coordinators, forkers, and their configured delegates. External dependencies OpenOracle, WETH/REP token behavior, Ethereum execution and finality, RPCs, wallets, and proof data. Untrusted actors Traders, vault owners, liquidators, disputers, bidders, settlers, keepers, and indexers. Failure outcomes Reverted calls, delayed transitions, underfunded child repair, stale prices, incorrect external data, or unreconstructable history. The assumptions below explain what must remain true for these assets and transitions to retain their intended safety or liveness properties; they are not additional contract guarantees.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-value","heading":"Participation and coordination","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions explain why someone performs the economically useful action instead of merely being permitted to perform it.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a01","heading":"Participants act on risk-adjusted economic incentives","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Enough participants prefer actions with greater expected value after gas, fees, delay, capital lockup, and risk. The model does not rely on altruistic defense. Failure boundary: A nominally profitable liquidation, dispute, bid, migration, or fork defense may not happen when its risk-adjusted return is unattractive. Mechanics: economic roles , liquidation incentives , auction clearing , and oracle incentives .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a02","heading":"Required actions remain worth their operating costs","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each time-sensitive action, transaction fees, opportunity cost, coordination cost, and capital lockup remain small enough relative to the value protected or earned. Failure boundary: An action can be technically executable but economically abandoned before its deadline. Mechanics: auction gas bounds , oracle gas parameters , and escalation deployment costs .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a03","heading":"Each economic role has enough independent participants","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Trading, liquidation, escalation, migration, auction bidding, price correction, and settlement have enough non-colluding participants for the relevant action. Secondary-market trading is external to Statoblast. Failure boundary: Thin or controlled participation can remove price discovery, competitive bidding, or an opposing outcome deposit. Mechanics: economic roles , fork coordination , and auction participation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a04","heading":"Participants can mobilize REP to raise an alarm","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Market participants can mobilize enough vault-backed REP through vault owners to fund an early competing Invalid , Yes , or No deposit and keep the local contest active while additional capital reacts. Failure boundary: A wrong local outcome can become final before better-capitalized participants enter. Mechanics: escalation resolution , escalation accounting , and operator guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a05","heading":"Participants value continued access to the supported Statoblast lineage","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The participants whose coordination determines durable use value continued access to the supported Statoblast lineage at least as highly as its configured statoblastSecurityMultiplierBps basis points of the value secured by that lineage. BPS_DENOMINATOR = 10_000 converts that stored value to its human multiplier. Value Statoblast access × BPS_DENOMINATOR ≥ statoblastSecurityMultiplierBps × Value secured Compare one lineage’s continued-use value and secured value at the same pre-attack time. Failure boundary: Participants may rationally abandon, grief, or coordinate away from a lineage whose continued-use value is too small. Mechanics: fork security , fork migration , and Statoblast security boundary .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a06","heading":"At least one executor advances every required public transition","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"An adequately funded participant or service monitors and calls each needed permissionless transition, including liquidation initiation, local escalation deposits and settlement, fork activation, child creation, vault and share migration, auction start and finalization, bid settlement, OpenOracle report settlement, settled-report callback recovery, overflow operation execution, and proof-based claims. Failure boundary: Permissionless state can remain stalled even though no contract authorization prevents progress. Mechanics: lifecycle liveness , fork operations , and oracle operations .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-forks","heading":"Forks, truth, value, and liquidity","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions connect onchain branching and accounting to offchain truth judgments, asset value, liquidity, and migration.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a07","heading":"A supported lineage preserves enough aggregate child value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each protected Statoblast origin lineage, evaluate the parent and its child-pool continuations in one numeraire at the fork-security decision horizon, before long-run value has fully concentrated on one branch. The sum of traded child-lineage values remains large enough compared with that parent lineage’s pre-fork value under the origin’s immutable statoblastSecurityMultiplierBps . Each child asset is valued in its own branch; unrelated external collateral is not counted again. Value parent lineage ≤ ∑ child ∈ Lineage continuations Value child × BPS_DENOMINATOR statoblastSecurityMultiplierBps lineage Parent and child values use the same origin lineage, valuation source, numeraire, and decision horizon. For multiplier 20_000 BPS , aggregate child-lineage value must be at least twice parent-lineage value; multiplier 30_000 BPS requires three times that value. Failure boundary: Value destruction or fragmentation can make a malicious or unnecessary fork cheaper than the harm it causes. Mechanics: fork economics , fork coordination , and pool migration .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a08","heading":"REP market capitalization approximates economic value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At the stated valuation time and system scope, REP market capitalization is a sufficiently accurate observable proxy for the discounted cash flow participants expect from REP. Failure boundary: Thin, manipulated, or speculative pricing can overstate the economic value securing open interest. Mechanics: REP economics and economic backing claims .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a09","heading":"Users value the universes they judge truthful","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users prefer the universe or universes they judge truthful, so economic activity and durable asset value concentrate there. Unlike A07’s fork-security decision horizon, this assumption describes the later, durable allocation of value after users coordinate. Value before fork ≈ Value truthful universe When several branches remain plausibly truthful, document the value split instead of assuming one winner. Failure boundary: Coordination on a false branch, or durable fragmentation across branches, defeats the value-concentration security argument. Mechanics: Zoltar security and child outcome resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a10","heading":"Participants can access timely outcome evidence","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users can obtain enough reliable real-world evidence before the relevant protocol deadline to judge which universe is truthfull. Failure boundary: Missing, delayed, or conflicting evidence can split honest capital or let a false local outcome become final. Mechanics: question encoding , invalid outcomes , and child resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a11","heading":"Migration is operationally practical","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users, exchanges, interfaces, custodians, and other integrations are willing and able to migrate assets and state into the child universe or universes they support during the applicable windows. Failure boundary: Supported branches can remain illiquid or undercollateralized because economic claims and backing do not move there. Mechanics: REP splitting , pool migration , and operator migration guidance .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a12","heading":"Truth auctions can convert supported child REP efficiently","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"When repair is needed, enough demand can buy some supported-child REP for ETH at an acceptably small discount and operating cost. This does not assume that the full repair target is raised. Failure boundary: Weak or strategically withheld demand leaves the child operational with impaired collateral. Mechanics: auction lifecycle , clearing , and repair accounting .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a13","heading":"REP is sufficiently liquid","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can acquire or sell the required REP quantities without price impact or delay large enough to invalidate reporting, disputing, liquidation, auction, or migration economics. Failure boundary: Capital may exist in principle but cannot be assembled at a usable price before the deadline. Mechanics: economic roles , escalation , and oracle positions .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a14","heading":"REP economic value exceeds the open interest it secures","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For a stated pool, lineage, or system scope at one timestamp, open interest and REP discounted cash flow are valued in the same numeraire, and REP discounted cash flow remains strictly greater. For a pool-level evaluation, open interest means the pool’s protocol-accounted ETH collateral unless the analysis explicitly names a broader exposure measure. The REP supply and price basis must also be stated; market capitalization is the observable proxy in A08 . This external relationship is separate from each pool’s onchain multiplier-adjusted REP backing check. Open Interest < Discounted Cash Flow REP Record scope, timestamp, common numeraire, price source, REP supply basis, and open-interest definition when evaluating this strict inequality. Failure boundary: REP holders may rationally accept or cause losses larger than the durable REP value placed at risk. Mechanics: security pools , liquidation backing , and oracle exposure analysis .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a15","heading":"Question selection remains aligned with user intent","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces and users verify the immutable question ID and metadata when selecting a market or pool, and the question has evidence and wording from which participants can reach an outcome or intentionally choose Invalid . Failure boundary: Unverified selection can route users and capital to an unintended market or pool, while ambiguous wording or unavailable evidence can split local outcome coordination. Mechanics: UNI-02 , question identity , and pool selection .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-oracle","heading":"Oracle correction, inclusion, and operational data","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions cover truthful REP/ETH price discovery, deadline access, capital, independent correction, and the offchain information required to act.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a16","heading":"Deadline transactions can obtain Ethereum inclusion","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Honest users can submit competitively priced Ethereum transactions and obtain canonical inclusion within every relevant dispute, escalation, migration, auction, staging, and settlement window. Failure boundary: Censorship or congestion lasting through a deadline can finalize a wrong report or prevent a supported action. Mechanics: deadline invariants , auction windows , and oracle censorship model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a17","heading":"OpenOracle has an available, adequately capitalized corrector","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one OpenOracle participant continuously monitors pending REP/ETH reports, can identify candidate errors using the reference market in A19 , can fund the scenario-dependent WETH and REP replacement position plus transaction costs, and can obtain inclusion before settlement. The participant's net incentive to perform the correction is the separate boundary in A18 . Failure boundary: An incorrect report can settle and authorize price-sensitive pool actions when no capable participant detects, funds, and includes a correction. Mechanics: REP/ETH oracle , correction incentives , and attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a18","heading":"At least one available corrector has an independent net incentive","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one participant satisfying A17 is not under common control with the sponsor-funded coordinator reporting path, does not share the manipulation payoff, and is not bribed or compensated. The participant's net payoff from correcting is strictly greater than its net payoff from leaving the harmful report uncorrected. Two external submissions are not required for every request. Failure boundary: A sponsor-controlled, bribed, or economically indifferent corrector can make the contestable report path behave like a single trusted reporter. Mechanics: settlement validation scope , economic tradeoffs , and oracle invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a19","heading":"A robust reference market makes harmful REP/ETH errors correctable","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Independent participants can observe a manipulation-resistant reference REP/ETH price. Every price error large enough to unlock a security-relevant pool action also leaves a correction opportunity that remains profitable after actual gas, prevailing priority fees, OpenOracle fees, price impact, and required capital lockup. Failure boundary: A harmful deviation below the profitable correction threshold, or a manipulated/illiquid reference market, can settle without an economically rational challenge. Mechanics: report sizing , external-payoff model , and settlement limits .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a20","heading":"Canonical chain data and proof construction remain available","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can access reorg-aware Ethereum history, recognized contract addresses, OpenOracle report state, and the event data needed to construct carry and nullifier proofs before use. Failure boundary: A valid dispute, settlement, migration, or inherited claim can become practically unavailable even while its contract entrypoint remains live. Mechanics: event replay , carry proofs , and operator orientation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-technical","heading":"Assets, clients, deployment, Ethereum, and cryptography","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions define the technical foundation below the protocol’s onchain guards.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a21","heading":"Genesis REP and configured WETH obey the required accounting model","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Genesis REP and configured WETH move exactly the requested base units, use 18 decimals, return supported ERC-20 values, and do not rebase, charge transfer fees, invoke unexpected callbacks, blacklist protocol contracts, pause required transfers, or permit unauthorized minting. WETH remains redeemable 1:1 for ETH, the genesis theoretical supply is accurate, and the configured genesis REP burn sink remains inaccessible. On mainnet these token behaviors are external assumptions. On Sepolia, the reviewed GenesisReputationToken and WETH9 enforce issuance and transfer behavior, provided A23 verifies their deployed bytecode and wiring and A26 preserves Ethereum execution. The configured burn sink's inaccessibility remains an external assumption on Sepolia as well as mainnet. Canonical child REP is likewise implemented by the reviewed ReputationToken contract rather than being an additional external-token assumption. Failure boundary: Origin-pool, genesis-fork, or oracle accounting can overstate balances, backing, burned supply, or WETH value. Mechanics: EXT-01 , genesis REP handling , and oracle token pair .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a22","heading":"Participant-controlled recipients can accept required asset deliveries","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Vaults, bidders, oracle sponsors, traders, share holders, and other caller-controlled recipients use addresses that accept native ETH when claiming fees, collateral, proceeds, or refunds. Contract recipients also return the required ERC-1155 receiver selector when complete-set creation, share migration, or an outcome-share transfer delivers tokens to them. A rejected ETH push may revert or become bidder-specific deferred credit; a rejected ERC-1155 callback reverts the mint or transfer and its surrounding protocol action. No protocol path can force a rejecting address to accept a later delivery or redirect every payout or mint. Failure boundary: A contract account that rejects ETH or the required ERC-1155 callback can strand its own payout or make its complete-set, migration, transfer, or claim action unavailable even while protocol-wide accounting remains safe. Mechanics: ETH callback safety , ERC-1155 callback safety , share-token receipt , auction refunds , oracle request refunds , and pool payout guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a23","heading":"Users select verified canonical deployments","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The reviewed release is built reproducibly with its pinned compiler and toolchain configuration. Release artifacts identify the manifest addresses and reviewed bytecode hashes. Users and integrations compare locally rebuilt and deployed runtime bytecode with those reviewed hashes, then verify constructor-installed dependencies, factory and forker provenance, pool lineage, question ID, and manifest. Constructor admission is not a safety proof. Failure boundary: Toolchain drift, unavailable or mismatched reviewed hashes, permissionless lookalike instances, wrong dependencies, or unintended lineage and question wiring can bypass the reviewed protocol even when the selected bytecode functions as configured. Mechanics: release posture , deployment status , compiler configuration , OpenOracle build provenance , deployment bytecode , and authority invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a24","heading":"Clients, RPCs, and wallets preserve user intent and canonical state","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces, indexers, RPC providers, and wallet software return canonical, reorg-aware state; construct and display the intended chain, target, calldata, parameters, and ETH value; and submit only transactions the user authorizes. Users verify transaction previews rather than treating an application label as an onchain guarantee. Failure boundary: Compromised or stale offchain software can misroute signatures or capital, hide deadlines or events, or cause users to authorize valid but unintended calls without violating contract guards. Mechanics: deployment discovery , contract interactions , and operator verification .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a25","heading":"Immutable parameters preserve economic and executable security margins","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The selected parameter relationships keep the fork threshold and haircut economically meaningful, the initial escalation deposit affordable, the pool multiplier and effective pool-held vault REP backing multiplier large enough for their distinct coverage roles, the effective pool-held vault REP backing multiplier at least the 10,500-BPS liquidation-award reserve, the oracle target error profitable to correct after fees and gas, dispute and settlement windows usable, and callback and transition gas executable. The Statoblast pool statoblastSecurityMultiplierBps is distinct from openOracleSecurityMultiplierBps . Failure boundary: Weak or internally inconsistent immutable parameters can defeat the economic or liveness argument even when the canonical contracts function exactly as configured. Mechanics: Statoblast parameters , fork economics , liquidation economics , oracle parameters , and oracle attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a26","heading":"Ethereum preserves the execution environment the protocol relies on","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Ethereum continues to provide correct EVM execution and atomic rollback, sufficient canonical finality, usable timestamp and block.basefee semantics, and block gas limits and gas schedules under which every mandatory immutable transition remains executable. Failure boundary: Deep reorganizations, invalid state execution, adversarial time behavior, or gas repricing can reverse accepted state or strand required transitions. Mechanics: reorg handling , atomicity and deadlines , and bytecode and gas bounds .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a27","heading":"Ethereum cryptographic primitives remain secure","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Keccak-256 remains collision- and preimage-resistant at the truncated widths used by the protocol, and CREATE2 identity remains sound. secp256k1 ECDSA signatures remain unforgeable with correct EOA signer recovery for Ethereum transactions and EIP-712 authorizations. OpenOracle's fixed Permit2 singleton additionally validates either an EOA signature or an ERC-1271 contract-account signature correctly. OpenOracle fixes the Permit2 address, requires the exact permitted amount, sends the requested tokens to itself, and constructs the witness from the beneficiary, relayer, token owner, and intent. Permit2 defines the EIP-712 domain, deadline, permitted-amount ceiling, unordered-nonce replay protection, and EOA/ERC-1271 signature validation. Verification under A23 must cover the pinned Permit2 implementation at that fixed address. Failure boundary: Forged identities, colliding questions or universes, false carry proofs, forged EOA transactions, incorrect EOA recovery or ERC-1271 acceptance, or broken domain and nonce enforcement can violate otherwise-correct guards or authorize an unintended token transfer. Mechanics: universe identity , proof hashing , Permit2 deposit authorization , Permit2 signature interface , pinned Permit2 implementation provenance , and deterministic deployments .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a28","heading":"Participants retain control of account authority","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants protect the private keys, signing devices, session permissions, and delegated ERC-20 or ERC-1155 approvals that authorize their accounts. Account recovery and custody arrangements do not give an unintended party effective control during a security-sensitive window. Failure boundary: A stolen key, compromised signer, or overbroad approval can authorize valid but unwanted transfers, deposits, bids, migrations, or configuration changes without violating a contract guard. Mechanics: transaction authority , vault and pool operations , and account-authority monitoring .","title":"Security model","topic":"Safety","weight":0},{"fragment":"excluded-guarantees","heading":"Guarantees deliberately not made","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These outcomes remain possible without contradicting the security model. They should be included in user, operator, and integration risk disclosures. EG01. Zoltar creates valid branches but does not enforce that the fork question is relevant to a user, establish the objectively truthful branch, obtain user consent to a fork, or force users to coordinate on one branch. EG02. A truth auction may raise less than its repair target; the child then activates with migrated collateral plus accepted auction ETH. EG03. OpenOracle report liquidity does not cap the notional value of operations using an accepted price, and settlement does not prove external price correctness. EG04. A participant with unbounded capital can keep funding valid disputes and delay the sponsor lane indefinitely; no bounded coordinator-availability claim is made. EG05. The immutable protocol has no administrator who can pause, upgrade, roll back, or rescue an unsafe deployment after launch. EG06. Escalation principal and expected rewards are not senior to open-interest repair. A child truth auction may proportionally reduce them, and a losing escalation claim pays its committed depositor nothing.","title":"Security model","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["operators","guardrails","launch","recovery"],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Operators, indexers, reviewers, and UI maintainers consult this reference for contract-accurate guardrails, launch procedures, and edge-case behavior in one subsystem at a time. The explanations carry the full protocol story. Each section answers a different operational question: how the immutable launch posture constrains releases, what a security pool or escalation game will and will not accept, how fork migration behaves, how auction settlement works, and how the REP/ETH coordinator stages or recovers operations. Implementation guardrails map to their contract sources so operators can check each rule against the exact Solidity implementation. The Protocol Invariants catalog connects those local guardrails to cross-contract conservation, lifecycle, liveness, and economic security properties, including launch-critical properties that the current contracts do not preserve. The Protocol Security Model is normative for the protocol's external assumptions.","title":"Operator guardrails","topic":"Operations","weight":1},{"fragment":"security-review-orientation","heading":"Security Review Orientation","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before classifying an economic or liveness concern, use the security model's grouped assumptions to identify external dependencies, then follow its canonical mechanics links. The invariant catalog owns the current requirement, status, and evidence for EXT-05 recursive-fork gas behavior . Operator work relies particularly on A23 verified deployments , A06 lifecycle executors , A20 chain-data and proof availability , A26 Ethereum execution and finality , A27 cryptographic security , A15 intended question selection , A25 safe immutable parameters , A22 asset-recipient compatibility , A24 client, RPC, and wallet integrity , and A28 account authority .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"assumption-monitoring","heading":"Assumption Monitoring","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The security model is the canonical definition of each boundary. This table assigns operational evidence and response for assumptions whose truth can change with market or service conditions. Evaluate each signal at the same lineage, pool, timestamp, numeraire, and decision horizon as the protected action. Assumptions Operational evidence Review cadence Response when the boundary is weak or unknown A01, A02, A03, A06 Expected reward after gas, fees, delay, and capital lockup; number and independence of active liquidators, escalation participants, bidders, correctors, and transition executors; settle-ready OpenOracle reports, callback-recovery candidates, and overflow operations Continuously for deadline-bearing flows; before launch and after parameter or gas-regime changes Fund or arrange independent executors, reduce exposed value, or disclose that the affected transition may stall A05, A07, A09, A11 Parent and supported-child lineage value in one numeraire; observed user, custodian, exchange, interface, and proof-tool readiness to migrate assets and state within the applicable windows; documented continued-use value and secured value Before a fork-security claim and throughout migration and coordination windows Do not claim fork deterrence, practical migration, or value concentration; narrow supported lineage and exposure A08, A13, A14 REP supply basis, price source, depth and price impact; REP discounted-cash-flow methodology; protocol-accounted ETH collateral or explicitly broader open interest At launch, continuously while open interest exists, and after material price, supply, liquidity, or exposure changes Cap or stop new exposure in clients and disclosures; do not describe REP backing as economically sufficient A04, A10 Outcome evidence sources and availability; immediately mobilizable REP and transaction budget before each local-escalation deadline From question creation through local resolution and any fork window Mark the market or action impaired, surface the deadline, and avoid asserting that honest local correction remains fundable A12 Auction demand, indicative REP/ETH depth, expected discount, and bidder operating cost Before and during every repair auction Disclose likely under-repair and plan operation of the child at only migrated collateral plus accepted auction ETH A16, A26 Inclusion delay, base fee, priority fee, reorg depth, block gas limit, and gas schedule against each immutable window and mandatory transition Continuously while a deadline or mandatory transition is live Raise transaction fees where rational, use independent submission paths, and disclose missed-deadline or stranded-transition risk A17, A18, A19 Independent corrector availability, ownership and payoff independence, scenario-dependent WETH and REP replacement capital, reference-market depth, manipulation cost, correction profit after fees/gas/impact, transaction budget, and settlement inclusion Continuously while a report is pending or a cached price can authorize operations Block or discourage new price-sensitive operations and disclose that an incorrect report may settle A21, A22 Mainnet external-token code and behavior; Sepolia reviewed Genesis REP/WETH runtime bytecode, constructor allocations, and wiring; WETH redeemability; burn-sink status; recipient ETH/ERC-1155 compatibility At deployment verification and before first use of a new recipient or integration Reject the deployment or incompatible recipient; do not rely on redirected recovery A20, A23, A24, A27, A28 Pinned compiler and toolchain identity; reproducible-build output, reviewed bytecode hashes, release manifest, deployed runtime code and wiring; independent reorg-aware RPC/event replay; hash and CREATE2 verification; signer, typed-data domain, nonce, witness, session, and approval inventory At release, on every environment or account change, and continuously for indexers Stop signing or operating, reject an unverifiable deployment, revoke compromised authority where possible, and re-establish canonical state from independent sources A15 Immutable question ID and metadata, evidence plan, and pool relevance Before pool selection Reject or warn on the selection; do not represent an admitted question as relevant or resolvable A25 Deployed immutable values and every documented economic, timing, integer-width, and gas relationship Before deployment and after any change in external gas or market conditions used by the model Treat the immutable deployment as unsupported; there is no administrative parameter repair","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"immutable-protocol-release-posture","heading":"Immutable Protocol Release Posture","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Zoltar and Statoblast are intended to launch as immutable, permissionless contracts. Launch documentation should not assume an operator can pause, upgrade, roll back, or disable protocol behavior after deployment. Release work should instead publish verifiable provenance: final commit and tag, deterministic addresses, CI and local QA results, production UI artifact hash, and any known limitations. UI deployments may describe data freshness and route users to verified artifacts, but they must not be documented as emergency controls over the protocol. This is the protocol's EG05 no-pause, upgrade, rollback, or rescue boundary .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"launch-release-checklist","heading":"Launch Release Checklist","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before tagging a launch release, run bun run ui:build:prod from a fresh dependency install and run bun run check:mainnet-deployment . This command fails when either generated deployment manifest is stale. Refresh and review both manifests with bun ./scripts/check-mainnet-deployment.mts --write before rerunning the check. That command writes and checks both mainnet-deployment-addresses.json and sepolia-deployment-addresses.json . Use the pinned compiler configuration in solidity/ts/compile.ts , the exact tool versions in solidity/package.json , and the imported OpenOracle profile in UPSTREAM.md . Publish the reproducible-build runtime hashes with the release, keyed to the manifest addresses, and compare both a clean local rebuild and deployed runtime code with those reviewed hashes before marking the release canonical. Push the final v* tag only after the CI Gate succeeds on the same commit. The Build and Push to IPFS workflow must succeed for that tag, and the resulting GitHub release must record the IPFS hash emitted from the published artifact. Production UI release notes should state that ?simulate=1 is a browser-local sandbox. Mainnet and Sepolia quote-dependent actions use live RPC data plus available network-local Uniswap liquidity, while simulation uses its configured mock where supported. The OpenOracle integration guide owns the quote-source order and network-address details. RPC and client integrity remain covered by A24 , while quote availability and representativeness are official-client limitations rather than protocol security assumptions. Stale, unavailable, or unsupported quotes are production blockers for the affected client action, not inputs that should be replaced with simulation prices.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"genesis-rep-deployment","heading":"Genesis REP Deployment","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Sepolia genesis REP allocations are canonical source data in sepoliaRepAllocations.ts . The configured 11 million REP mint cap is divided equally among the listed holders; adding a holder automatically reduces every holder's allocation. Integer division may leave the total minted supply below the cap by less than one attoREP per holder. Review both the holder list and the computed bigint total before deployment. The GenesisReputationToken constructor requires at least one allocation, matching holder and balance array lengths, unique nonzero holders, nonzero balances, and a total supply no greater than 11 million REP. It emits the standard ERC-20 mint Transfer event once for each allocation. The sum of the constructor balances is immutable and is returned by getTotalTheoreticalSupplyAttoRep() . There is no administrator, post-deployment mint, allocation correction, or recovery entrypoint. An incorrect allocation must be fixed in the source list before deployment and the deterministic manifests must then be regenerated. Because constructor arguments are part of CREATE2 init code, any allocation change produces a new genesis REP address and new addresses for every deployment step whose constructor wiring depends on it. Never continue a deployment from a manifest generated for a different allocation list. A future release that intentionally changes starting holders must follow the same source review and full-manifest regeneration procedure.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"security-pool-guardrails","heading":"Security Pool Guardrails","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool creation, minting, withdrawal, and direct-ETH rules are collected here in contract-first form. Participant-controlled payout and share-receiving addresses must satisfy A22 asset-recipient compatibility . Deterministic identity depends on A27 cryptographic security , while vault and token authority depends on A28 account authority . Area Implementation behavior Source Origin pool shape Origin pools require an existing question, an unforked universe, a present universe REP token, and exactly two categorical labels in this order: Yes , then No . Statoblast adds Invalid as the third trading and resolution outcome. At construction, the effective escalation deposit is exactly max(1 REP, theoretical REP supply / 10,000,000) ; a new origin's non-decision threshold must exceed that effective value. A zero configured vault REP floor selects the default theoretical supply / 100,000 ; a nonzero constructor value is the exact override. The independently configured security-bond debt floor defaults to 1 ETH. SecurityPoolFactory.sol , ShareToken.sol , BinaryOutcomes.sol Deployment history The factory records security-pool deployments and exposes paged deployment-history reads for indexers and UIs. SecurityPoolFactory.sol Deterministic addresses SecurityPoolFactory derives securityPoolSalt = keccak256(abi.encode(parent, universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas)) , using a zero parent for an origin. The coordinator and child truth-auction factories each hash that value again with their caller ( SecurityPoolFactory ) for their CREATE2 salt. The pool deployment worker instead uses literal CREATE2 salt zero; its address varies through the full constructor init-code hash, which contains the pool wiring. An origin share token uses originId = keccak256(abi.encode(questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas, originUniverseId)) directly as its CREATE2 salt, while factory ownership, Zoltar, and question ID remain in its init code; children reuse that lineage token and inherit its priority fee. SecurityPoolFactory.sol , SecurityPoolDeployer.sol , PriceOracleManagerAndOperatorQueuerFactory.sol , UniformPriceDualCapBatchAuctionFactory.sol , ShareTokenFactory.sol Share-token salt squatting Direct ShareTokenFactory callers cannot reserve the canonical origin-pool share-token address. CREATE2 includes constructor arguments in the init-code hash, and the share token owner is msg.sender , so a direct caller using the canonical salt deploys a caller-owned token at a different address than the SecurityPoolFactory deployment. ShareTokenFactory.sol , ShareToken.sol Complete-set capacity Complete-set minting checks the next collateral amount against live ETH minting capacity derived from total REP capacity ownership, the current REP/ETH price, and the pool multiplier. It also requires any explicit unassigned auction position to remain healthy after the mint. Sold truth-auction ownership enters both total ownership and fee eligibility at finalization, so it accrues fees before the bidder claims it; its health affects minting, not fee accrual. Claiming later assigns that already-counted ownership and its fees to the bidder vault without adding it to either pool aggregate again. The pool updates accounting before the share-token mint call. SecurityPool.createCompleteSet , SecurityPool.depositRepToVault Fee accrual clamp Fee accrual is clamped to the question end time while the universe is unforked; after a universe fork, the accumulator is clamped to the fork time. SecurityPool.sol Fee accrual and rounding Fee-eligible capacity includes live vault-assigned ownership and sold truth-auction ownership awaiting claim. Settlement-collateral decay first enters an unallocated reserve; vault checkpoints and auction claims preserve fractional carry and move only whole, redeemable attoETH into totalClaimableVaultFeesAttoEth . totalAccruedFeesAttoEth() combines reserve and assigned claimable vault fees for settlement-collateral reconciliation. Global fee-index and sub-attoETH carries preserve value between accruals. Capacity-ownership-scoped index carry is cleared when pool financials are installed, ordinary vault capacity ownership changes, or liquidation transfers capacity ownership, so prior attribution dust is not reassigned. Assigning finalized-auction ownership to its winning vault is the exception: it preserves that carry because the sold ownership was already fee-eligible and already part of the denominator. After a fork permanently ends accrual, the pool tracks capacity ownership still awaiting the final index. Auction claims preserve aggregate fees; once every eligible vault and auction allocation is reconciled, a subsequent permissionless vault fee checkpoint returns any whole reserve attoETH that no vault can individually claim to settlement collateral. SecurityPool.updateSettlementCollateral , SecurityPool.updateVaultFees , SecurityPool._clearFeeIndexRemainder Retention-rate updates Retention-rate updates no-op when the pool is not Operational ; otherwise utilization divides tracked collateral by live ETH minting capacity derived from total capacity ownership. Unclaimed sold auction ownership therefore already affects utilization and fee accrual. A later claim changes only its vault assignment, not fee eligibility, total ownership, live capacity, or retention solely because of that claim. SecurityPool.sol Multiplier-preserving REP outflows withdrawRepFromVault rejects withdrawal while the vault still has REP escrowed in an escalation game. Withdrawals and new escalation deposits independently preserve multiplier-adjusted REP backing for the affected vault and aggregate pool at the latest valid REP/ETH price. SecurityPool.sol External fork withdrawal lock If the universe forked before the local escalation game ended and non-decision was not reached, parent-pool escalation withdrawal reverts. The child continuation already has the canonical snapshot and aggregate backing: winning inherited deposits settle there by proof, inherited losers require no transaction, and clearing the vault's unresolved parent escalation-deposit accounting is optional. SecurityPool.sol , EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep REP-to-backing-unit round-up The pool’s internal REP-to-backing-unit conversion uses ceiling division when it removes vault REP backing units for an escalation deposit. That intentionally removes enough REP backing units to cover the requested REP even when the conversion is fractional. SecurityPool._attoRepToBackingUnitsRoundUp , SecurityPool.depositToEscalationGame Escalation deposit wrapper depositToEscalationGame rejects pools with an inherited fixed outcome because they cannot enter another fork or safely unwind a later local non-decision. Otherwise it deploys the game on the first valid post-end deposit, previews the accepted amount, removes vault REP backing units with round-up accounting, checks local and global multiplier-adjusted backing, transfers REP into the game, and records the deposit. The game factory normally preserves the configured bond; if later REP burns reduce the live threshold to that bond or below, it uses nonDecisionThresholdAttoRep - 1 so long as the threshold exceeds one attoREP. SecurityPool.depositToEscalationGame , EscalationGameFactory.deployEscalationGame , EscalationGame.recordDepositFromSecurityPool Direct ETH receiver Ordinary calls to receive() accept ETH only from the forker, its truth auction, or its parent pool. Forced ETH can bypass receive() ; it remains raw, unaccounted surplus and is not collateral or accrued fees. SecurityPool.sol Vault enumeration getVaults(startIndex, count) pages the append-only vault registry in newest-registered-first order. Registration requires only a nonzero address and can occur without economic state. Consumers must read current REP backing, capacity ownership, claimable fees, escalation stake, and bad debt when deciding whether to display an entry. The call returns an empty array when count == 0 or the start index is out of range; getVaultCount() reports the registry length. SecurityPool.getVaults","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"share-migration","heading":"Share Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Share migration after a fork is user-facing asset migration, not vault REP migration. Canonical rules live in FORK-10 for persistent per-child materialization, SHARE-04 for the economic-claim denominator, and FORK-11 for unequal materialized supplies. Area Implementation behavior Source Persistent entitlement ShareToken.migrate preserves the caller's parent token balance as a branch-independent entitlement. After its first use that source balance is transfer-locked, preventing a seller and buyer from materializing the same claim. ShareToken.sol Target list The target outcome list must be non-empty, valid for the fork question, and strictly increasing. ShareToken.sol Canonical source and fork transition Migration requires a canonical source pool for the token's universe. If that pool is still Operational , migrate first asks its forker to initiate the pool fork; the call proceeds only after the source is PoolForked . That conditional transition can freeze the source pool and emit the ordinary pool-fork and snapshot events. ShareToken.migrate , SecurityPoolForker.initiateSecurityPoolFork Canonical destinations Every destination must be a canonical direct child of the source pool. A single-target migration may lazily create a missing child while the branch-creation window is open; a multi-target migration requires every canonical child pool to exist before the call. Lazy creation can also emit child-link, REP-migration, and continuation events. ShareToken.migrate , SecurityPoolForker.createChildUniverse Independent materialization Each selected child token id is minted up to the current source balance. A later call can select another existing child, and source shares added through an ancestor migration materialize only their previously unminted delta. ShareToken.sol Economic denominator Child setup copies the frozen parent's remaining economic claim supply rather than its currently materialized ERC-1155 supply. Complete-set minting and redemption update that denominator; share migration does not because its claims were reserved at fork time. SecurityPoolForker.sol , SecurityPool.sol Timing The eight-week window bounds Statoblast child-pool creation and pool-local vault/REP migration. Shares can materialize indefinitely in an already-created child. Raw Zoltar child-universe deployment and migration-REP splitting follow Zoltar's fork and balance guards, not this pool timestamp. ShareToken.sol , SecurityPoolForkerVaultMigrationBase.sol , Zoltar.sol Malformed outcomes Malformed fork outcomes are rejected using Zoltar question-data validation. ZoltarQuestionData.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"escalation-resolution-and-deposits","heading":"Escalation Resolution and Deposits","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Accepted deposits, edge-case resolution results, and carry-proof or residual-REP consumption all live in this section. Area Implementation behavior Source Deposit preview The preview path expects a proposed amount of at least the current startBondAttoRep . EscalationGame.previewDepositOnOutcome Recorded deposit amount The recorded deposit must be positive, and the accepted amount must be at least startBondAttoRep unless it exactly fills the selected outcome to nonDecisionThresholdAttoRep . EscalationGame.recordDepositFromSecurityPool , EscalationGameCalculations._getAcceptedDepositAmount Outcome room Accepted deposit amount is capped to the selected outcome's remaining room under nonDecisionThresholdAttoRep . EscalationGameCalculations._getAcceptedDepositAmount Tie adjustment If the accepted amount would create a tie with the current maximum balance while still below non-decision, the contract reduces the accepted amount by 1 attoREP ; if that breaks the accepted-amount rule, the deposit is rejected. EscalationGameCalculations._getAcceptedDepositAmount Unresolved resolution state If two or more outcomes meet the current running cost, getQuestionResolution() returns None . EscalationGameCalculations.getQuestionResolution Matching-fork continuation resolution After the continuation deadline, a game with a fixed child outcome settles deposits against that outcome. Payout settlement rejects any pool/game outcome mismatch. See Matching-question child outcome for the pool-level finality rule. EscalationGameCalculations.getQuestionResolution , EscalationGameSettlement._getPayoutQuestionResolution , SecurityPoolForker.getQuestionOutcome Empty-game fallback If all outcome balances are zero after the running cost is non-zero, getQuestionResolution() returns Invalid ; if the running cost is still zero, the unresolved check returns None first. EscalationGameCalculations.getQuestionResolution Strict leading resolution After the unresolved-cost check and the all-zero Invalid fallback, a strict Invalid , Yes , or No lead returns that outcome. Valid local deposits prevent tied maxima below non-decision by reducing the accepted amount by 1 attoREP , or reverting if that adjusted amount becomes invalid. Continuation snapshots preserve the parent balances exactly, including ties, so every selected branch starts from the same unresolved game state. EscalationGameCalculations.getQuestionResolution , EscalationGameCalculations._getStrictLeaderOrNone , EscalationGameCalculations._getAcceptedDepositAmount , EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances Structural non-decision predicate hasReachedNonDecision() becomes true when two or more outcomes reach nonDecisionThresholdAttoRep ; nonDecisionState separately records how that balance condition entered the lifecycle. New games use ⌈forkThresholdAttoRep / 2⌉ , so two threshold balances always contain at least the REP required to fund an own fork even when the fork threshold is odd. Zoltar.getNonDecisionThresholdAttoRep , EscalationGameCalculations.hasReachedNonDecision nonDecisionState = None No explicit non-decision transition has occurred. Deposits may remain available subject to the ordinary activation, continuation, timing, and amount guards. EscalationGame.previewDepositOnOutcome , EscalationGameDepositDelegate.recordDepositFromSecurityPool nonDecisionState = Local A local deposit brought a second outcome to the threshold. The game stores the real nonDecisionTimestamp , closes further deposits, and canTriggerOwnFork() returns true. That predicate is game-local: a pool with an inherited fixed outcome still rejects the fork transition in activateForkMode() . EscalationGameDepositDelegate.recordDepositFromSecurityPool , EscalationGameCalculations.canTriggerOwnFork , SecurityPool.activateForkMode nonDecisionState = InheritedThresholdTie Snapshot initialization preserved two or more threshold-full balances without fabricating a local timestamp. The game closes further deposits. With a fixed child outcome it follows the continuation clock and cannot trigger its own fork; without one, canTriggerOwnFork() returns true directly. EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances , EscalationGameCalculations.getEscalationGameEndDate , EscalationGameCalculations.canTriggerOwnFork Carry proofs Inherited carry uses Merkle Mountain Range peaks and nullifier roots so child games can consume proofs without replaying already-spent parent deposits. EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._verifyAndConsumeCarriedDepositProof Continuation withdrawal Anyone may relay a batch of winning carried proofs for one depositor after a child continuation resolves. A proof authenticates the original deposit and consumes its stable index once; the committed depositor remains the payout address. One mantissa/exponent ratio applies every intervening auction haircut exactly once to both proof amount and cumulative reward position without walking ancestors, and excludes losses before a continuation-local deposit was created. Consumption asks the immediate source game for retained cumulative-prefix differences, so per-leaf source-basis allocations telescope exactly to the aggregate checkpoint in every proof order. Inherited losing outcomes retire in constant-size work when the result is final and require no proof transaction; locally created losing deposits retain ordinary settlement. Child initialization freezes the carry root/count and retention metadata and performs no claim or owner import. The 64 MMR peaks are a logarithmic frontier, not a participant cap. Continuation resumes in one bounded call once aggregate backing is complete, and liquidation never processes or moves claims. SecurityPool.withdrawForkedEscalationDeposits , EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._consumeCarriedDeposit Optional unresolved parent escalation-deposit accounting cleanup The public SecurityPoolForker.migrateVaultWithUnresolvedEscalation wrapper first runs ordinary migration for that same vault: the parent REP backing-unit claim is converted to REP and credited as child-local backing units, capacity ownership moves to the child, claimable fees are checkpointed and retained in the parent vault, and proportional settlement collateral routes separately at pool level. Its fixed-size escalation cleanup then exports one Invalid / Yes / No principal tuple without another token transfer. The cleanup preserves proof leaves and neither funds dispute-staked REP backing nor authorizes inherited claims. SecurityPoolForker.migrateVaultWithUnresolvedEscalation , EscalationGameEscrow.exportVaultUnresolvedTotalsWithoutTransfer , EscalationGameForker.migrateVaultWithUnresolvedEscalation Residual sweep Once a game is final and unresolved principal is cleared, its residual can be settled. A continuation sweep first retires its inherited aggregate carry; after that, no local dispute stake or vault escrow may remain. An ordinary game's residual returns to the security pool. A fork continuation burns its entire residual, which can include inherited capital that must not accrue to child-local owners. This rule prevents capture by a late depositor and stranding in an ownerless fixed child. EscalationGameSettlement.sweepResidualRepToSecurityPool","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"fork-migration","heading":"Fork Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool-level migration mechanics after a universe fork live here: proxies, child-pool creation, pool-proxy REP splitting, and child outcome selection. Area Implementation behavior Source Pool-specific migration identity The forker lazily deploys one deterministic SecurityPoolMigrationProxy per parent pool. The proxy is the stable msg.sender for Zoltar migration accounting. REP sent to its predictable address before deployment remains isolated surplus: fork accounting uses newly routed REP and the Zoltar migration ledger, not the proxy’s preexisting ERC-20 balance. SecurityPoolForker.sol , SecurityPoolMigrationProxy.sol Proxy authority The migration proxy is owner-controlled by the forker and wraps lockRep , forkUniverse , splitToChild , and child REP sweeping. SecurityPoolMigrationProxy.sol Child REP backing During vault migration, the forker ensures the child pool is backed by enough child-universe REP for cumulative migrated REP credited to that child. SecurityPoolForkerVaultMigrationBase.sol Vault fees during migration Migration uses one fixed, fee-exclusive snapshot and a cumulative REP ceiling. The parent vault is checkpointed before its capacity ownership is cleared, so earned fees remain redeemable, and parent-to-child ETH transfers cannot consume the balance reserved by totalAccruedFeesAttoEth() . The Fork Migration Proportion and Fork Collateral Ceiling own checkpoint order, cumulative allocation, and repair derivation. SecurityPoolForker.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPool.sol Split shortfall tracking The forker tracks how much REP has already been split for each parent-pool/outcome pair and only splits the shortfall before sweeping child REP into the child pool. SecurityPoolForkerVaultMigrationBase.sol Canonical continuation snapshot Fork initialization stores the complete parent Invalid / Yes / No balances, carry totals, peaks, leaf counts, and nullifier roots once. Every lazily created child reads that same stored snapshot, even if an allowed parent claim occurs before a later child is created. SecurityPoolForker._snapshotEscalationAtFork , SecurityPoolForkerBase._initializeChildForkedEscalationGameIfNeeded , EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances Aggregate escalation backing An external unrelated fork reproduces the drained game's dispute-staked REP one-for-one in each selected child. The escalation's own fork transfers post-haircut aggregate backing instead; ordinary pool-held REP remains one-to-one. Each selected child receives its applicable aggregate backing once, and resumeFromFork remains paused until that backing is present after accounting for child REP already exported by valid direct pre-resume claims. See the canonical fork-migration derivation and FORK-08 for the raw and effective principal definitions and exact funding bound. Neither path scales or releases per-vault child escrow. SecurityPoolForker.initiateSecurityPoolFork , SecurityPoolForker.forkZoltarWithOwnEscalationGame , SecurityPoolForkerVaultMigrationBase._ensureChildEscalationBacking , EscalationGame.resumeFromFork Direct-claim replay protection A successful direct own-fork claim records both the stable parent deposit identity and cumulative claimed principal by outcome. Every current, late, or recursively inherited child rejects a second payout; effective inherited principal subtracts immediate-parent direct claims so those leaves cannot strand the residual sweep. EscalationGameForker.claimForkedEscalationDeposits , SecurityPoolForker.isEscalationDepositClaimedDirectly , EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep Optional vault cleanup See Optional unresolved parent escalation-deposit accounting cleanup for wrapper ordering and proof independence. In migration terms, the call records cleanup for one selected child and never processes another vault. SecurityPoolForker.migrateVaultWithUnresolvedEscalation , EscalationGameForker.migrateVaultWithUnresolvedEscalation Independent continuation liveness Child creation installs the canonical carry commitment, retention checkpoint, and aggregate backing without waiting for vault transactions or copying claims and owners. The fixed 64-peak MMR frontier is logarithmic commitment storage for up to 2^64 - 1 leaves, not a participant cap; see Merkle Mountain Range Carry Proofs . Once the child is operational and fully funded, resumeForkedEscalationGame resumes it in one bounded permissionless call. Authenticated winning proofs can then be relayed permissionlessly to pay their committed depositors, inherited losers retire without proofs, and optional parent cleanup may happen independently. SecurityPoolForkerBase._finalizeAwaitingForkContinuationIfReady , EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep Own-fork REP buckets When escalation triggers its own fork, escalationChildRepAtForkAttoRep equals disputeStakedRepToForkAttoRep - ⌊forkThresholdAttoRep / forkBurnDivisor⌋ . vaultRepAtForkAttoRep preserves ordinary pool-held REP one-for-one. Creating one selected child does not reduce the post-haircut escalation backing available to another selected child. SecurityPoolForker.forkZoltarWithOwnEscalationGame , SecurityPoolForker.getOwnForkRepBuckets , SecurityPoolForkerBase._initializeOwnForkRepBuckets Child-pool deployment window Child pools are created lazily for selected fork outcomes, but only while the parent pool is PoolForked and the eight-week migration window is still open. SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolUtils.sol Matching-question child outcome When the parent universe forks on the pool's question, the child stores its selected branch as a fixed result. depositToEscalationGame rejects new local deposits, and activateForkMode rejects every later pool fork transition. See Child Outcome Resolution for the collateral and REP-liveness rationale. SecurityPool.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolForker.sol Unrelated-fork child outcome A child created by an unrelated fork without an inherited fixed result uses a local escalation result only if that escalation ended before the universe forked; otherwise continuation or later state must produce the outcome. SecurityPoolForker.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"truth-auction-operations","heading":"Truth Auction Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Collateral-repair auctions have three operator-critical boundaries: forker ownership, a one-week bidding window, and paged settlement into vault accounting. Bids close at auctionStarted + AUCTION_TIME ; direct auction finalization is allowed at >= that boundary, but the public forker wrapper requires the boundary to have passed. The canonical clearing rules and examples are in Truth Auction . Area Implementation behavior Source Owner Child-pool truth auctions are owned by SecurityPoolForker . The direct auction startAuction and finalize calls are owner-only, while anyone can reach them through startTruthAuction and finalizeTruthAuction . UniformPriceDualCapBatchAuction.sol , SecurityPoolForker.sol Finalized settlement After finalization, only the auction owner withdraws bid outcomes from the auction; SecurityPoolForker wraps that call so anyone can settle a vault's bid pages. UniformPriceDualCapBatchAuction.sol , SecurityPoolForker.sol Child fee activation Completed migration or truth-auction settlement starts a new child fee epoch. Migrated ownership and sold truth-auction ownership enter the fee denominator at finalization. Each later auction claim assigns the bidder vault its ownership and fees accrued from the saved finalization index through the current index without changing the pool’s live eligible total; intervening capacity changes and liquidations remain preserved. SecurityPool.sol , SecurityPoolForker.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"repeth-oracle-operations","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The coordinator quick reference below covers staging, callback recovery, stale-operation handling, and liquidation boundaries. Report sizing, request cost, and current OpenOracle parameters are canonical in OpenOracle Integration . Area Implementation behavior Source Atomic initial report The sponsor funds the ETH bounty and WETH/REP position; the coordinator submits that position atomically as the initial reporter. A caller-selected WETH amount above the dynamic minimum is allowed. See OpenOracle sizing and funding for derivation, reporter withdrawals, application buffering, and escalation effects. OpenOraclePriceCoordinator.sol , openOracle.ts Immediate execution If a price is still valid, the operation executes immediately and positive unused ETH is refunded. A contract caller must accept the refund callback or the execution rolls back; the canonical refund warning also covers newly opened reports. OpenOraclePriceCoordinator.sol Fresh-price request guard requestPrice reverts while the cached coordinator price is still valid, so callers cannot open a redundant pending report on top of a fresh cache. OpenOraclePriceCoordinator.sol Staging guardrails Withdrawal and liquidation amounts must be non-zero. Validity must be positive and no more than five minutes. Withdrawals target the operator's own vault. Liquidation uses explicit operator, receiver, and target roles; receiver and target must differ. A delegated receiver requires a valid bounded approval and queue-time reservation, while the self-receiving operator path requires no signature. OpenOraclePriceCoordinator.sol Delegated receiver approvals LiquidationApprovalRegistry supports direct receiver approvals and EIP-712 permits from EOAs or ERC-1271 wallets. An approval binds pool, receiver, exact operator, exact or wildcard target, cumulative and per-operation ETH debt limits, minimum post-health factor, validity window, and nonce. Revocation blocks new reservations without disturbing pending ones; nonce invalidation blocks stale approvals. LiquidationApprovalRegistry.sol , openOracle.ts Approval reservation lifecycle Staging reserves at most requested debt, snapshotted target debt, per-operation allowance, and available cumulative allowance. Success consumes exactly moved debt and releases the remainder; bad-debt-only execution consumes zero. Every terminal failure and permissionless expiration releases once. Approval validity must cover the latest legal execution time. OpenOraclePriceCoordinator.sol , LiquidationApprovalRegistry.sol Pending report bound At most four operations are attached to one settlement callback. Operations can still be tracked as active even when they do not fit into the pending callback batch. OpenOraclePriceCoordinator.sol Sponsor exclusivity Once the cached price is stale and a caller funds a fresh coordinator report, only that pendingReportSponsor can append more staged operations until settlement. Valid disputes reset the settlement clock, so this paid exclusive lane can be extended indefinitely; the coordinator makes no bounded-availability guarantee. The economic tradeoffs , attack model , and parameter table own dispute funding, escalation, and rounding details. OpenOraclePriceCoordinator.sol , OpenOracle.sol High settlement basefee The callback clears the pending report and terminally fails every operation attached to that report if settlement basefee is above the stored maximum from request time. Each delegated-liquidation reservation is released. OpenOraclePriceCoordinator.sol Uneconomic or saturated final report Coordinator reports always enable dispute history. The callback reads storedGame(reportId).numReports . It rejects a saturated uint24 counter with Counter saturated ; otherwise it requires the final history record's WETH amount to cover the configured dispute-security formula at its recorded base fee plus the configured priority fee and rejects it with Report uneconomic . OpenOraclePriceCoordinator.sol , OpenOracle.sol Zero report values A callback with zero amount, zero denominator, or a computed zero price does not update lastPrice . OpenOraclePriceCoordinator.sol Recovery path Coordinator reports opt into OpenOracle's STORE_ALL and TRACK_DISPUTES flags. Recovery requires a pending report whose stored OpenOracle settlement timestamp is nonzero. It returns withdrawable reporter balances to the sponsor, clears the report, consumes all associated pending operations, and releases their liquidation reservations. An expired operation can also be cleaned permissionlessly before any valid-price requirement. OpenOraclePriceCoordinator.sol Consumed failures Expired operations, stale liquidations, zero-effect withdrawals, and liquidations too close to threshold are consumed and emitted as failed executions rather than retried forever. OpenOraclePriceCoordinator.sol Liquidation snapshot A staged liquidation becomes stale if target REP backing units or capacity ownership changes. The queue-time open-interest snapshot bounds the reservation and records context but is not itself a staleness key. Execution re-evaluates target and receiver health from live balances, live obligations, and the settled price. ETH debt moves only up to the amount whose complete 5% REP award is funded; REP-denominated capacity ownership moves proportionally. An unsuitable below-minimum receiver reverts instead of creating avoidable bad debt. See Queued Execution . OpenOraclePriceCoordinator.sol , SecurityPool.sol Liquidation distance A staged liquidation must remain at least minLiquidationPriceDistanceBps beyond the liquidation threshold when it executes. OpenOraclePriceCoordinator.sol , SecurityPoolLiquidationDelegate.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"support-module-inventory","heading":"Support Module Inventory","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The explanation focuses on protocol flow. The table below names the remaining support contracts that matter for integrators, code reviewers, and interface inventory work.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"caller-and-trust-boundaries","heading":"Caller and trust boundaries","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Deployment helpers are not interchangeable with canonical protocol discovery. Several are intentionally permissionless and namespace CREATE2 salts by msg.sender ; an application must discover canonical instances through SecurityPoolFactory , recognized pools, and recognized forker events rather than treating every factory-created address as canonical. Module Direct caller boundary Canonical-use boundary EscalationGameFactory deployEscalationGame and deployEscalationGameFromFork have no permission check, but a successful caller must implement the ISecurityPool reads used during construction; an EOA or incompatible contract reverts. The caller is bound into the new game as its securityPool ; the factory itself becomes the game's immutable owner and immediately calls start or startFromFork . Ordinary deployment requires a threshold above one attoREP and lowers a start bond at or above that threshold to threshold - 1 . The factory has no owner role and no later resumeFromFork relay. Accept a game only after EscalationGameSet from a recognized pool. In the supported continuation path, the owning pool calls resumeFromFork , which also verifies aggregate funding. ShareTokenFactory Anyone can call deployShareToken to deploy or retrieve a token under keccak256(abi.encode(msg.sender, salt, questionId)) ; that caller is the token's initial authorized address. Origin lineage tokens are the instances created by SecurityPoolFactory and named in DeploySecurityPool ; children reuse the parent's token. UniformPriceDualCapBatchAuctionFactory Anyone can call deployUniformPriceDualCapBatchAuction . Its salt is namespaced by caller and supplied salt, and its operational owner is the caller-supplied owner address. During external-universe initiation, the source must be authorized by its declared share token; own-game initiation does not perform that check, and neither relationship proves configured-factory registration. Child linking requires a deployed auction that this forker has never trusted before and preserves the source's factory and forker. Canonical pools remain the instances registered by the configured SecurityPoolFactory . PriceOracleManagerAndOperatorQueuerFactory Anyone can call deployPriceOracleManagerAndOperatorQueuer under a caller-namespaced salt with caller-supplied OpenOracle, REP token, and positive initialReportPriorityFeeAttoEthPerGas ; coordinator construction rejects zero and values that would consume the reserved OpenOracle uint128 report and escalation-halt capacity. The caller therefore chooses this immutable gas-price assumption within that bound. A pool coordinator is the instance named by canonical DeploySecurityPool ; SecurityPoolFactory atomically binds it to that pool before returning, and canonical child deployment inherits the value from the parent coordinator. SecurityPoolDeployer and SecurityPoolDeploymentWorker Only the configured SecurityPoolFactory may call the deployer; only that deployer may call its worker. They are code-size deployment plumbing. Pools become canonical only when the factory emits SecurityPoolRegistered and DeploySecurityPool . SecurityPoolMigrationProxy Only its immutable owner , the SecurityPoolForker that created it, may call lockRep , forkUniverse , splitToChild , or sweepChildRep . Its deterministic per-parent address is exposed by getMigrationProxyAddress and recorded in fork snapshot events; users do not call it. EscalationGameDepositDelegate , EscalationGameClaimDelegate , EscalationGameForker , SecurityPoolForkerVaultMigrationDelegate , and SecurityPoolLiquidationDelegate Their public methods are delegatecall implementations. The deposit delegate implements recordDeposit , recordForkedEscrowForOutcome , applyTruthAuctionHaircut , funding-gated resumeFromFork , consumeEscrowedRepForOwner , consumeUnresolvedRepForClaimOwners , creditClaimOwners , and creditExternalClaimOwners . The shared claim delegate handles retention checkpoint reads and initialization; it has no ownership or import surface. The forker delegates implement escalation and vault migration. The pool helper implements funded REP-backing-unit, capacity-ownership, receiver-debt, and full-request bad-debt liquidation accounting while isolating fees and claims, plus permissionless continuation resume. Direct calls operate on isolated or uninitialized helper storage and normally revert; they are not protocol actions. Accept resulting state and logs only when the recognized game, pool, or forker executes the matching wrapper through delegatecall . State changes and events then occur in that recognized contract's context. Canonical liquidation enters through SecurityPool.performLiquidation ; continuation progress enters through SecurityPool.resumeForkedEscalationGame . SecurityPoolEventEmitter.sol emitPoolAccountingCheckpoint , emitVaultAccountingCheckpoint , and emitForkSnapshotEvents are externally callable and payable , but direct calls execute against the helper's own storage and emit from the helper address. Payability permits delegatecalls from value-bearing protocol flows; callers must not send ETH directly because it is not protocol collateral and the helper has no recovery surface. Pools and the forker use constructor- or factory-installed modules through delegatecall , so logs are emitted from the recognized pool or forker address. Indexers must reject matching signatures from the helper or any unrecognized emitter. External-universe fork initiation requires declared-share-token authorization; own-game initiation does not. Child linking preserves the source factory and forker and rejects undeployed or previously trusted auctions. Configured-factory registration remains the canonicality boundary. Storage-layout tests protect the delegate's fixed slots. EscalationGameProofVerifier , MerkleMountainRange , and SecurityPoolUtils Stateless or library math and proof routines have no lifecycle authority. Public verifier calls can be used for previews, but do not mutate a game or pool. A result becomes protocol state only through a recognized game, pool, or forker transaction and its events. DeploymentStatusOracle Anyone can call getDeploymentMask ; the constructor alone fixes the ordered address list and emits DeploymentAddressesSet . It reports code presence only, not canonical wiring or readiness. Decode it using its constructor event or the matching deployment manifest order.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"securitypoolutils-read-surface","heading":"SecurityPoolUtils read surface","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"These external pure previews distinguish REP-denominated capacity ownership from ETH-denominated open interest. REP-per-ETH prices use PRICE_PRECISION , and health factors and security multipliers use basis points. The previews have no lifecycle, caller, freshness, or canonical-pool authority; only a recognized pool or coordinator transaction can apply their result. Function Exact boundary behavior Conceptual owner calculateFeeAccrual(settlementCollateralAttoEth, retentionRate, timeDelta, indexRemainder, feeEligibleCapacityOwnershipAttoRep, feesOwedRemainder) Applies fixed-point retention over elapsed time, carries the global index remainder, and returns whole credited fees plus the next fee remainder. Canonical callers avoid zero eligible capacity ownership before this division. Fee accrual calculateVaultFee(capacityOwnershipAttoRep, feeIndexDelta, remainder) Floors the vault's whole fee credit and returns the remaining fixed-point numerator for its next checkpoint. Fee accrual calculateMintingCapacityAttoEth(capacityOwnershipAttoRep, repEthPrice, securityMultiplierBps) Converts aggregate REP-denominated ownership into current ETH capacity using the live REP-per-ETH price and pool multiplier. Zero ownership or price returns zero. Dynamic capacity calculateVaultOpenInterestAttoEth(activeOpenInterestAttoEth, vaultCapacityOwnershipAttoRep, totalCapacityOwnershipAttoRep) Attributes live pool open interest pro rata to capacity ownership, rounding a positive vault share upward. Zero vault or total ownership returns zero. Dynamic capacity calculateBundledLiquidationTransfer(targetBackingUnits, targetCapacityOwnershipAttoRep, targetOpenInterestAttoEth, requestedDebtAttoEth, repEthPrice, currentPoolHeldAttoRepBalance, currentTotalRepBackingUnits, minimumRemainingAttoRep) Returns zero when the request, target open interest, capacity ownership, or price is zero. Caps a nominal debt quote by target open interest and by the complete 5%-bonus award fundable from target pool-held REP, then rounds proportional capacity ownership downward. Execution derives moved debt separately from the receiver's exact live open-interest increase. Partial requests preserve the configured REP minimum. On a full-target request, bad debt is target open interest minus exact moved debt and can include both an award-unfunded slice and integer-allocation residue. Vault liquidation isVaultHealthy(poolHeldVaultRepBackingAttoRep, disputeStakedAttoRep, openInterestAttoEth, repEthPrice, poolSecurityMultiplierBps) Checks the associated-REP and free pool-held REP requirements against live open interest. Both requirements round upward; zero open interest is healthy. isVaultHealthyAtFactor applies an approval-selected factor of at least 10,000 to both branches. Capacity and health calculateRetentionRate(settlementCollateralAttoEth, mintingCapacityAttoEth) Zero live minting capacity returns the maximum retention rate. Otherwise retention decreases linearly through the 80% utilization dip and remains at the minimum rate above it. Retention rate Area Named contracts and modules Role Deployment tracking DeploymentStatusOracle.sol Reports one code-presence bit per configured deployment-step address. See Deployment Status Oracle . Carry proof hashing MerkleMountainRange.sol , EscalationGameProofVerifier.sol , EscalationGameTypes.sol Defines the carried-deposit leaf shape, the 64-peak limit, proof-length rules, and nullifier-root replay protection. See Merkle Mountain Range carry proofs . Escalation game composition EscalationGame.sol , EscalationGameCalculations.sol , EscalationGameCarry.sol , EscalationGameClaimDelegate.sol , EscalationGameDepositDelegate.sol , EscalationGameEscrow.sol , EscalationGameSettlement.sol , EscalationGameState.sol , EscalationGameStorage.sol Splits the game across immutable ownership and storage, claim and deposit delegation, calculations, continuation proofs, escrow, and settlement without exposing those internal modules as separate canonical games. ERC-20 support ERC20.sol , IERC20.sol , IERC20Metadata.sol , Context.sol , SafeERC20Ops.sol , GenesisReputationToken.sol , ReputationToken.sol Base REP token implementation, interfaces, metadata, execution context, safe transfer wrappers, the constructor-allocated genesis token with fixed theoretical supply, and the inherited transfer , approve , and transferFrom entrypoints. ERC-1155 and token ids ERC1155.sol , ShareToken.sol , TokenId.sol , IERC1155.sol , IERC1155Receiver.sol , IERC165.sol , IShareToken.sol Outcome-share token plumbing, interface support, token-id encoding, inherited setApprovalForAll , and both single and batch safeTransferFrom forms. Factories and deployers EscalationGameFactory.sol , SecurityPoolFactory.sol , SecurityPoolDeployer.sol , ShareTokenFactory.sol , UniformPriceDualCapBatchAuctionFactory.sol , PriceOracleManagerAndOperatorQueuerFactory.sol Deterministic deployment entrypoints for pool, share-token, oracle-coordinator, escalation-game, and auction instances. Migration, liquidation, and storage modules SecurityPoolStorage.sol , SecurityPoolLiquidationDelegate.sol , SecurityPoolMigrationProxy.sol , SecurityPoolForker.sol , SecurityPoolForkerAuctionSettlementBase.sol , SecurityPoolForkerBase.sol , SecurityPoolForkerStorage.sol , SecurityPoolForkerTypes.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolForkerVaultMigrationDelegate.sol , EscalationGameForker.sol , SecurityPoolEventEmitter.sol , LiquidationApprovalRegistry.sol , SignatureValidation.sol Defines shared pool storage, delegated liquidation accounting and bounded approval reservations, ECDSA/ERC-1271 signature validation, funded continuation delegatecalls, fork-time state, truth-auction settlement, vault and unresolved-escalation migration, event encoding, and stable proxy identity used while routing parent state into child pools. Protocol interfaces IEscalationGame.sol , ISecurityPool.sol , ISecurityPoolForker.sol , ISecurityPoolForkerChildEscalationGameInitializer.sol , IUniformPriceDualCapBatchAuction.sol , IWeth9.sol , IAugur.sol Typed boundaries used by games, pools, migration delegates, auctions, WETH integration, and external Augur compatibility. Protocol utilities Constants.sol , ScalarOutcomes.sol , BinaryOutcomes.sol , SecurityPoolUtils.sol , Multicall3.sol , WETH9.sol Shared constants and math, outcome representations, Multicall3 aggregate , tryAggregate , tryBlockAndAggregate , blockAndAggregate , aggregate3 , aggregate3Value , and block-context reads, plus standard WETH9 deposit , withdraw , approval, and transfer support. Imported oracle integration UPSTREAM.md , OpenOracle.sol , Errors.sol , ISignatureTransfer.sol , interfaces/IERC20.sol , interfaces/IERC165.sol , token/ERC20/IERC20.sol , IERC1363.sol , SafeERC20.sol , Panic.sol , ReentrancyGuard.sol , StorageSlot.sol , utils/introspection/IERC165.sol , Math.sol , SafeCast.sol OpenOracle's imported source, exact SlimStorage revision, compiler profile, and dependency provenance are owned by UPSTREAM.md . The coordinator submits attoETH/attoREP reports with dispute history and full game storage enabled; OpenOracle's packed event stream remains separate from Zoltar replay.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"","heading":"","keywords":["deployment","bitmask","runtime bytecode","status"],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"DeploymentStatusOracle is a deployment-progress helper. It does not measure protocol readiness or initialization success. It only reports which configured deployment-step addresses currently contain runtime bytecode. The canonical mainnet and Sepolia manifests list this contract directly, so operators and frontends need a precise explanation of the returned bitmask and the built-in 256-step ceiling.","title":"Deployment status oracle","topic":"Deployment","weight":1},{"fragment":"mask","heading":"What getDeploymentMask() Returns","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor stores one ordered address[] deploymentAddresses . getDeploymentMask() loops over that array and checks deploymentAddresses[index].code.length . When code exists, the oracle sets bit index in the returned uint256 . This is a code-presence bitmap. A set bit means code exists at the configured address. A clear bit means the address currently has no code. The oracle does not verify constructor args, ownership wiring, or post-deployment setup. A row of deployment steps maps by index into bit positions in a uint256 mask. Each step sets its bit only when code exists at that configured address. Deployment Status Mask Each configured deployment step uses one bit position in the returned word. The oracle sets that bit only if the step's address currently contains code. deployedMask = ∑ i = 0 codePresent ( i ) ⋅ 2 i Each configured deployment step contributes one bit position to the returned word.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"ordering","heading":"How Bits Map To Deployment Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Bit positions are array positions. Bit 0 maps to the first constructor address, bit 1 to the second, and so on. Offchain decoding must use the same ordered list that seeded the oracle. The constructor emits DeploymentAddressesSet(address[] deploymentAddresses) with that exact ordered list. It is the onchain source for recovering a particular oracle instance's bit mapping; the manifest below describes the current planned deployment and must match the constructor event for that instance. The current UI and deployment helpers derive the constructor list from deploymentSteps with one exception: the oracle does not include its own address in its constructor array. The manifest still lists deploymentStatusOracle as a deployment step, but the UI tracks that step out-of-band and starts consuming mask bits from the remaining addresses. The tables below render the complete bit mappings directly from the canonical mainnet and Sepolia deployment manifests, excluding deploymentStatusOracle . Each manifest's ordered deploymentSteps array is the canonical mapping for that network. Bit Mainnet step id Label Status in entered mask Loading the canonical manifest… Bit Sepolia step id Label Loading the canonical manifest… If the manifest's constructor order changes, the rendered bit meanings change with it; this page does not maintain a second list. The manifest's derivedContracts list is separate. Those addresses are not part of the deployment-status mask unless they also appear in the constructor array. Decode a deployment mask Mask value Loading the canonical mapping… Decoder controls will be available when the mapping loads. Retry loading the canonical mapping","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"limit","heading":"Why The Cap Is 256 Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor requires deploymentAddresses.length <= uint256(type(uint8).max) + 1 , which is 256 . The limit exists because the result is a single uint256 , and each tracked step consumes one bit. Indices 0...255 are supported. A deployment process with more than 256 tracked addresses must split status across multiple masks or use a different representation.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Interpretation and Verification Boundaries","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Evidence What it proves What it does not prove Bit set Runtime bytecode exists at the configured address. Correct bytecode, constructor values, wiring, permissions, or operational readiness. Bit clear No runtime bytecode was observed at that address by this call. Whether the address is intentionally empty or the manifest is wrong. Manifest match The decoder used the intended ordered address list for the network. That deployed code matches the release or that RPC responses are honest. Full deployment verification Code hashes, required wiring, permissions, and manifest provenance can be checked together. Economic safety or future operational liveness. This page documents the deployment-status bitmask only; it does not provide complete bytecode, wiring, permission, or manifest verification. The getDeploymentMask() return type is uint256 ; the constructor reverts when more than 256 addresses are supplied.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Static decoder test vectors","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Mask Meaning 0x0 No tracked constructor address contains runtime code. 0x1 Only constructor address index 0 is present. 0x5 Constructor address indices 0 and 2 are present. 2^255 Only the highest supported bit, index 255, is present. The interactive tables require JavaScript. The ordered source lists are available in the Mainnet and Sepolia manifests; bit i always corresponds to the i th constructor address after excluding deploymentStatusOracle .","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"","heading":"","keywords":["MMR","carry proof","hashing","nullifier"],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Statoblast uses a Merkle Mountain Range only for inherited escalation carry. Parent escalation games export unresolved deposits into compact snapshots, and child continuations later verify withdrawals against those snapshots without replaying the full parent history onchain. The hashing primitives live in MerkleMountainRange.sol . Snapshot storage, proof structs, peak bounds, and replay protection live in EscalationGameTypes.sol , EscalationGameCarry.sol , and EscalationGameProofVerifier.sol .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":1},{"fragment":"leaf-shape","heading":"Leaf Shape And Hash Order","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each leaf hashes one unresolved deposit with depositor , outcome , amount , parentDepositIndex , cumulativeAmount , and sourceNodeId . Leaf hashes use keccak256(abi.encode(...)) . Parent hashes use keccak256(abi.encodePacked(left, right)) . Field Why it is committed depositor Binds the proof and payout to the original depositor. This ownership is immutable: liquidation moves only pool-held vault REP backing and cannot acquire or split the committed escalation claim. outcome Separates invalid, yes, and no carry domains. amount Commits to the source principal. parentDepositIndex Provides the stable identity later consumed by nullifiers. cumulativeAmount Preserves payout-order prefix data. sourceNodeId Distinguishes otherwise identical leaves copied from different local nodes. Hash order matters. Internal nodes always hash left before right , so proofs are position-sensitive inside each peak.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Normative Encoding","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The leaf ABI types are (address,uint8,uint256,uint256,uint256,uint256) , in the order depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId . outcome is the Solidity enum ordinal. Leaf hashing uses standard ABI word padding through abi.encode ; parent hashing uses the 64 raw bytes from abi.encodePacked(left, right) . There is no additional domain separator. leafHash = keccak256(abi.encode(depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId)) parentHash = keccak256(abi.encodePacked(left, right)) root = bagPeaks(occupiedPeaks, peakCount) Proof verification rejects an empty or out-of-range peak, leafIndex >= 2^peakHeight , a sibling array whose length is not peakHeight + peakCount - 1 , a nullifier path whose length is not 64 , a root mismatch, or an already-consumed nullifier.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Two-leaf conformance vector","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Input or intermediate Value Leaf 0 ABI values 0x0000000000000000000000000000000000000001, 0, 1, 0, 1, 0 Leaf 0 hash 0xb01042d963a0c261d64893dfb7e1f221c93ef608653cb92f9ca2c8899ca4572b Leaf 1 ABI values 0x0000000000000000000000000000000000000002, 1, 2, 1, 2, 1 Leaf 1 hash 0xe0ca39e8852dc96783e8b8f0859cfbf155338f9144ba4b9fdd6af847299fc867 Peak 1 / root 0x1c9c7f3c2c8bebf92ce393deb5dccdd253f2b10084af72defb5dcd4486832b27","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"peaks","heading":"Peaks And The 64-Peak Bound","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The carry snapshot stores one peak per set bit in the leaf count. The system fixes MERKLE_MOUNTAIN_RANGE_MAX_PEAKS = 64 , and carry initialization requires each snapshot leaf count to be less than 2^64 . That does not cap the snapshot at 64 leaves. It caps the number of peak positions. Any leaf count from 0 up to but not including 2^64 is valid because its binary decomposition fits inside 64 peak slots. A leaf count of thirteen is shown as binary one one zero one, corresponding to occupied peaks at heights zero, two, and three which are later bagged into one root. MMR Peaks The occupied peak heights are exactly the set bits in the snapshot leaf count. Those occupied peaks are later bagged into one root. To form one root, the verifier collects occupied peaks in ascending height order and then bags them from right to left with bagPeaks() . snapshotLeafCount < 2 64 The 64-peak constant means each inherited snapshot leaf count must fit below 2^64 . Local carry appends behave like binary addition with carries: a new leaf merges upward through occupied lower peaks until it finds the first empty peak slot. If that upward carry would reach height 64 , the append reverts with MMR too tall .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"proofs","heading":"Proof Structure","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"A carried-deposit proof has two parts. First it proves membership in the inherited MMR snapshot for one outcome. Then it proves that the same parentDepositIndex has not already been consumed in this continuation's nullifier tree. Component Purpose leafIndex , merkleMountainRangePeakIndex , merkleMountainRangeSiblings Proves the carried deposit belongs to the inherited snapshot root for that outcome. nullifierSiblings Proves the nullifier leaf is still empty so the carried deposit cannot be replayed. The membership lane combines the carried deposit leaf with bottom-up siblings inside its selected peak, then with other occupied peak roots in ascending height order to reconstruct the snapshot root. The nullifier lane hashes the stable parent deposit index and combines it with a fixed-depth sibling path to prove the claim has not already been consumed. Carry Proof Anatomy The MMR sibling array first reconstructs one selected peak and then supplies every other occupied peak. The independent 64-level nullifier path prevents the same stable deposit identity from being consumed twice. The index semantics are stricter than the field names might suggest. merkleMountainRangePeakIndex is the occupied peak height, not the ordinal position of that peak among occupied peaks. leafIndex is the leaf's offset inside that selected peak, so the verifier requires leafIndex < 2^merkleMountainRangePeakIndex . For example, if the full snapshot has 13 leaves, its occupied peak heights are 0 , 2 , and 3 . A proof for a leaf inside the height- 2 peak uses merkleMountainRangePeakIndex = 2 and a peak-local leafIndex in 0...3 , not the leaf's global index across all 13 leaves. merkleMountainRangeSiblings is also ordered in two phases. The first merkleMountainRangePeakIndex entries are the bottom-up Merkle siblings inside the selected peak. The remaining entries are the other occupied peak roots in ascending peak-height order, skipping the selected peak height. That exact ordering is why the verifier checks merkleMountainRangeSiblings.length == peakHeight + peakCount - 1 . Separately, the verifier requires nullifierSiblings.length == NULLIFIER_DEPTH with NULLIFIER_DEPTH = 64 . Nullifier paths are keyed by uint256(keccak256(abi.encode(parentDepositIndex))) , so the stable parent deposit index is the replay-prevention identity. Plan a carry proof Choose a snapshot leaf count and one occupied peak. The planner reports the peak-local index bound and the exact sibling-array lengths enforced by the verifier. It does not construct hashes or replace an offchain proof builder. Snapshot leaf count Occupied peak height Height 2 Peak-local leaf index Binary leaf count 1101₂ Occupied peak heights 0, 2, 3 Selected peak capacity 4 leaves; local indexes 0…3 MMR sibling hashes required 4 Nullifier sibling hashes required 64 Selection Valid peak-local index","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"snapshots","heading":"Snapshots In Child Continuations","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each continuation outcome stores both an inherited snapshot baseline and a mutable current state. Field Meaning snapshotLeafCount and snapshotPeaks The immutable inherited carry commitment. currentLeafCount and currentPeaks The descendant carry snapshot after inherited snapshot initialization plus local carry appends and local carry-leaf removal. Inherited proof consumption is tracked by currentNullifierRoot and unresolved totals rather than by mutating these peak fields directly. inheritedUnresolvedTotalAttoRep and localUnresolvedTotalAttoRep The principal totals the current carry state must still represent. currentNullifierRoot The replay-protection root after any carried proof has been consumed. The continuation API exposes the current descendant carry snapshot through getForkCarrySnapshot() and pages only local unresolved leaves through getCarryLeafPageByOutcome() . The immutable inherited baseline remains in snapshotPeaks and snapshotLeafCount inside outcome state, while getForkCarrySnapshot() reports the current carry peaks, current leaf counts, current totals, and current nullifier roots after local appends and proof consumption.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"sources","heading":"Sources","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"MerkleMountainRange.sol EscalationGameProofVerifier.sol EscalationGameCarry.sol EscalationGameTypes.sol","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"","heading":"","keywords":["Statoblast","markets","security pools","overview"],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Prediction Market draft This white paper develops the Statoblast prediction-market design, its REP-backed resolution path, and its security assumptions. The documentation overview introduces the system; the sections below provide the detailed protocol model and accounting rules. Related topics: documentation overview , escalation game , Zoltar , and security model . It is designed so that: Honest reporting is economically rewarded. Dishonest reporting becomes increasingly expensive. Anyone can challenge an incorrect resolution by risking REP. Markets continue operating even when participants fundamentally disagree. Every trader can ultimately settle their position according to the outcome they believe is truthful, even if multiple realities continue to exist simultaneously. Statoblasts design is based on Artic Tern Oracle and Sisyphean Exchnge .","title":"Statoblast protocol","topic":"Protocol","weight":1},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast consists of two separate protocol identities. Zoltar is the base forking oracle layer for universes, questions, and REP branching. Augur Statoblast is the prediction market layer that adds market collateral (open interest), local disputes and fork resolution mechanisms Statoblast's lifecycle starts by creating a pool scoped to one question and universe, letting traders use it and vault owners fund its security vaults, trying to resolve disputes locally, and only then turning to fork. A question is registered in Zoltar and a Statoblast security pool is deployed for that question in one universe. Vault owners deposit REP into security vaults and select a target factor for each deposit. That instruction determines only the incremental REP-denominated capacity created by that deposit. The current REP/ETH price converts the pool's aggregate ownership into live ETH minting capacity without iterating through vaults. After the market ends, REP reporters try to resolve the market outcome locally through the escalation game. In the unfortunate case where the escalation game raises sufficient amount of REP without reaching a decision, Statoblast triggers a Zoltar fork. An unrelated external Zoltar fork can also interrupt the pool before local resolution. After a fork occurs in Zoltar, a migration period begins in each Statoblast pool. Each vault holder chooses a market outcome, converts the parent backing-unit claim to REP in that child, and routes a proportional share of that pool's settlement collateral . If a child pool is missing ETH collateral after migration, it sells child-universe REP through a Truth Auction to repair that collateral gap. The child pool in the branch users choose to keep using resumes operation and users settle or redeem positions there. The lifecycle is shown in the system decision-flow diagram below: operation leads to local escalation, then either settlement or fork migration, auction repair, and child settlement.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"2. Protocol participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"#","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Traders / Liquidity providers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit ETH to mint complete sets containing transferable Yes , No , and Invalid outcome shares. Pay time-based fees in ETH to vault owners for the duration that the complete sets remain outstanding. Trade by splitting complete sets into their individual outcome shares. This trading occurs outside the protocol. Burn complete sets to recover ETH before resolution, or redeem individual winning shares for ETH after the market resolves.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Vault owners","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit REP into a security pool. Provide REP-backed security capacity for traders. Receive time-based fees in ETH from traders holding outstanding complete sets. Participate in the Escalation Game after the market ends. Choose the child where their parent REP claim becomes child-local backing units and receives its allocated share of pool settlement collateral after a fork. Keep the value of their REP backing above the amount required to secure their allocated open interest. Trigger OpenOracle games when a REP/ETH price is required for a protocol operation.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"REP holders","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Decide whether to create a security pool for a question. Decide whether to participate in an existing security pool. Split their REP into outcome-specific child-universe REP during a Zoltar fork.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"OpenOracle disputers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"OpenOracle disputers, such as arbitrage bots, monitor active OpenOracle games and challenge inaccurate REP/ETH prices when doing so offers a profitable arbitrage opportunity.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Truth auction participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"After a Zoltar fork, truth auction participants provide ETH to acquire REP-backed positions from an auction. Their ETH helps repair missing settlement collateral in a child security pool.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Liquidation receivers and operators","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"A receiver vault accepts moved security-bond debt and receives the REP backing-unit and capacity-ownership award. An operator monitors vault health, submits the liquidation, and pays its gas and oracle costs; the protocol does not compensate the operator merely for submitting. The same account can fill both roles through the self-receiving route, or a receiver can grant a bounded approval for a separate operator. See Capacity and delegated liquidation for the complete role, approval, price-distance, and execution lifecycle. Base required REP is the vault's live proportional open interest multiplied by the settled REP-per-ETH price, rounded upward. A target fails ordinary health when either required backing branch fails: pool-held REP + dispute-staked REP ≥ pool security multiplier × base required REP pool-held REP ≥ max(migration security multiplier, liquidation reserve multiplier) × base required REP The implementation rounds each required-REP branch upward. The ordinary target-health check applies a 10,000 BPS factor. Liquidation eligibility additionally requires the configured minimum price distance and the operation's other live guards. A delegated approval may demand a higher factor on both branches for the receiver's post-transfer health; it does not raise the target's liquidation threshold.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"3. Security Pools","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# The purpose of security pools is to ensure that all open interest is supported by sufficient REP. Aggregate capacity limits new minting, while each vault's live safety independently requires both the associated-REP and pool-held-REP backing branches described below. This requirement protects outcome-share holders by making dishonest behavior economically unprofitable. If a vault owner attempts to manipulate the market's resolution, the REP they risk losing should be worth more than any potential gain. The protocol therefore rewards behavior that supports correct resolution and penalizes behavior that threatens it.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"dynamic-capacity","heading":"Deposit Target and Dynamic Capacity","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Each positive REP deposit made for fee-earning capacity includes a deposit target factor of at least 10,000 BPS . The deposit adds ⌊deposited REP × 10,000 / deposit target factor⌋ of REP-denominated capacity ownership. Solidity rounds this quotient downward, so an extremely high target factor can make a valid deposit add zero attoREP of ownership. Deposits with different targets combine in one vault; the latest instruction is not a vault-wide target or health measurement. Capacity ownership aggregates at pool level in constant time and determines the vault's fee share. The pool's one Statoblast security multiplier and the current REP/ETH oracle quote convert total capacity ownership into ETH minting capacity: capacity ownership ÷ REP per ETH ÷ security multiplier . A higher REP-per-ETH quote means each REP is worth less ETH and therefore lowers live ETH capacity; a lower quote raises it. Neither change rewrites vaults or erases existing open interest. Vault health and liquidation instead compare live obligations against the vault's actual REP and proportional capacity ownership at the current price. The associated-REP-per-capacity ratio counts pool-held REP plus REP attributed to the vault in the current escalation game. That dispute stake is at-risk security, not liquid collateral with a known payout. The pool-held-REP-per-capacity ratio counts only REP immediately held by the pool. Neither ratio is current vault health: current health also depends on proportional open interest, the REP/ETH price, the security multiplier, and both the associated-REP and pool-held-REP constraints. Withdrawals burn capacity ownership in proportion to the REP removed. They update backing and capacity without selecting a new deposit target.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Fee Extraction and Retention Rate","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Being vault holder is valuable as it gives a permission to earn fees from traders that are holding complete sets or share tokens. Simply by holding REP, you are not entitled any fees in the system. Fee extraction and retention vary with vault capacity ownership and utilization; see Open Interest Fees for the complete fee curve and epoch accounting. read more from here on how fees work","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Shares and Complete Sets","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"ShareToken is an ERC-1155 contract. Each complete set mints one Invalid , one Yes , and one No share. The token id encodes the universe id and outcome, making positions fork-aware. ETH mints Invalid, Yes, and No shares. Operational pools redeem full sets, and finalized winning shares redeem through outcome settlement. Share Lifecycle Complete sets mint one Invalid, Yes, and No share; operational redemption burns the complete set, while finalized settlement redeems the winning outcome.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"escalation","heading":"4. Escalation Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# When a market ends, Statoblast attempts to resolve its outcome by running an Escalation Game based on The Isonzo Front . Participants escrow vault-backed REP behind Invalid , Yes , or No . Each deposit adds to that outcome's cumulative balance; it does not have to outbid the previous deposit or the current leader. The accepted deposit must normally meet the game's starting bond, unless it fills the selected outcome to the non-decision threshold. The cumulative binding-capital threshold increases with elapsed time, and the median outcome balance determines the scheduled end. Individual deposits still use the fixed start bond. After that deadline, a strict balance leader resolves the question. If two outcomes instead reach the non-decision threshold, the game enters local non-decision and Statoblast proceeds to its fork-resolution path. Why not fork immediately? A fork asks the whole universe to branch, so Statoblast first gives vault-backed REP a local path to settle the question. The rising cost curve makes late disagreement increasingly expensive, while the non-decision threshold keeps the fork path available when competing outcomes remain strongly funded. The Escalation Game system is an optimization, and in theory the system would work without it as well, in a more inefficient manner. read more from here on escalation game works","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"7. Forks and Migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# If the Escalation Game fails to reach consensus and enough REP has been committed to satisfy Zoltar's fork threshold, the game triggers a Zoltar fork and the fork resolution process begins. A pool can also enter fork resolution when another Zoltar application forks the same universe. The pool then migrates to the child universes, runs a truth auction if needed, and continues there. A pool moves from active operation through fork, child deployment, REP and collateral migration, and any auction repair to an operational child. Fork Timeline The parent pool pauses, vault owners select child universes, and each child resumes only after migration and any required collateral repair. Fork Steps The parent pool enters fork mode. Anyone can deploy a child pool for a valid fork outcome. Vault owners can convert their parent backing-unit claim to REP, credit that amount as child-local backing units, and move capacity ownership to one child of their choosing. Claimable fees remain checkpointed, funded, and redeemable in the parent vault. Unresolved ongoing escalation games fork into child pools and the games continue after a pause The child starts a truth auction after the migration deadline if collateral is missing. After the truth auction ends, winning bidders settle into REP backing units and capacity ownership. Vault owners claim winning own-fork escalation deposits; the own-fork path is open only during the parent migration window. A successful direct claim invalidates that stable deposit identity in every current and future child continuation. Shares migrate into new universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"8 week migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Vault holders have eight weeks to choose a child universe. Migration happens one pool at a time: a vault's parent backing units determine both its snapshot REP claim and its share of that pool's settlement collateral. The REP claim becomes child-local backing units. No collateral moves from other pools or markets in the universe. During the migration period, vault owners choose which child universe to support by migrating their positions into the corresponding child security vault. This preserves disagreement by splitting the parent market into separate branches. However, ETH is not duplicated across those branches. Each child pool receives only the pool-level settlement collateral routed to it, transferred capacity ownership, and converted child-universe REP backing credited as child-local units. For this mechanism to remain secure, the value of the pool-held REP backing must exceed the value of the pool settlement collateral it moves. Otherwise, a malicious vault could shift a significant portion of this pool's collateral to a universe in which they hold favorable positions. Example: backing-unit and settlement-collateral migration Bob's vault has 5 backing units out of 20 backing units in this pool. At the fork snapshot, the parent pool has 40 REP represented by those 20 units and 10 ETH of settlement collateral, so this example uses a 2 REP per backing-unit conversion. When the oracle forks into REP A and REP B , Bob believes universe A is correct and migrates to universe A. As a result: Bob's vault receives 10 child-local backing units , equal to the 10 REP A produced by the snapshot's 2 REP per parent backing-unit conversion. A proportional share of this pool's settlement collateral moves to universe A: 5 backing units 20 backing units × 10 ETH = 2.5 ETH Bob's 5 of 20 pool backing units route 25% of this pool's 10 ETH settlement collateral, or 2.5 ETH, to the selected child. The remaining vault owners choose universe B. Only this pool's collateral is divided; other markets and pools are unaffected. Their vaults receive the remaining 30 child-local backing units , equal to the corresponding 30 REP B . The remaining proportional share of this pool's settlement collateral moves to universe B: 15 backing units 20 backing units × 10 ETH = 7.5 ETH The remaining 15 of 20 pool backing units route the remaining 7.5 ETH of this pool's settlement collateral to universe B. After migration: Universe A credits Bob with 10 child-local backing units and receives 2.5 ETH of this pool's settlement collateral. Universe B credits the other owners with 30 child-local backing units and receives 7.5 ETH of this pool's settlement collateral. Escalation principal, rewards, carried claims, and externally supplied dispute-staked REP have zero parent-OI migration power. A losing participant would otherwise rationally route dispute-staked REP toward a false child merely to preserve valuable OI attached to it. Only pool-held vault REP backing determines a vault's migration weight. vaultMigrationPoweredAttoRep = vaultTotalAssociatedAttoRep − vaultEscalationGameAttoRep Escalation claims are excluded. Migration safety uses only pool-held vault REP backing. The effective multiplier is floored at 10,500 BPS so free backing normally funds the complete liquidation award. The exact live health rules are owned by the liquidation design . This separation lets REP perform escalation work without letting contingent claims secure parent open interest. A vault's settlement-collateral migration weight is its pool-held vault REP backing share of all pool-held vault REP backing at the fork. migrateVault converts the parent backing-unit claim to REP, credits that REP amount as child-local backing units, transfers capacity ownership to one selected child, and clears both parent fields. The REP-backing share separately routes proportional pool-level settlement collateral, while the transferred capacity ownership determines the vault's live proportional open-interest allocation in the child. Claimable fees are checkpointed and remain funded and redeemable in the parent vault. The vault cannot divide or reuse its migration state across sibling universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"child-outcome-resolution","heading":"Child Outcome Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Child pools resolve outcomes through the forker. When the parent universe forks on the pool's question, each child stores its selected Zoltar outcome as the fixed result, whether the fork began through the pool-specific path or a direct Zoltar call. The pool stores and reports that result from child creation, and it is final for that pool. Pool asset redemptions begin after the child becomes operational. Question, pool, escalation, fork, child migration, auction repair, fixed-outcome settlement, and unresolved recursive continuation flow. System Decision Flow The lifecycle connects question registration, pool operation, local escalation, fork migration, auction repair, settlement, and recursive continuation when a child remains unresolved.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"Shares migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"When a fork occurs, share migration locks the source balance and accepts an ordered list of child targets. The first materialization gives each untouched selected child the full current source balance; later calls mint only that child's unmaterialized delta. Shares do not disappear from the source or split into a pro-rata remainder. On first materialization into untouched children, the full current parent balance materializes in each selected child token id while the source remains locked as an entitlement. Later calls mint only the unmaterialized delta for each child. Share Migration Share migration is not a pro-rata split across selected branches. The source balance remains as a transfer-locked entitlement after first use, and each untouched selected child first materializes the full current balance. Later calls mint only that child's unmaterialized delta.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"auction","heading":"8. Truth Auction","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# After migration, a child pool may still have less ETH than it needs to support its open interest. A Truth Auction can sell some child-universe REP for ETH to repair that shortfall. The auction has an ETH raise target and a cap on how much REP it may sell. The cap depends on migrated REP, pool-held REP, and unresolved dispute-staked REP; it may allow the full auctionable amount, reserve a small amount, apply a migration haircut, or be zero. The child resumes with the collateral actually migrated and raised. See the Truth Auction design for timing, caps, rounding, bidding, and settlement. See the collateral-repair illustrations Parent settlement collateral 50 ETH Migration-routed collateral 47.5 ETH Auction ETH raised 2.5 ETH A child pool before auction, after full auction repair, and after weak auction demand settles at the collateral actually raised. Truth Auction Balance Sheet The auction compares received settlement collateral with the parent settlement target and shows any remaining shortfall. Child collateral repair progress Collateral Repair Progress Blue shows settlement collateral routed during migration, green shows auction repair, and the dashed line marks the parent settlement target. Migration-routed collateral Auction repair Dashed line: parent settlement target Routed collateral 47.50 ETH Initial shortfall 2.50 ETH Remaining shortfall 0.00 ETH Repair status no contribution required","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"oracle","heading":"9. REP/ETH Price Oracle","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast uses a REP/ETH price to evaluate pool security. The price feed supports operations whose safety depends on REP value relative to pool settlement collateral.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"statoblast-glossary","heading":"Glossary","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Capacity ownership A REP-denominated, price-independent share of pool minting capacity and fee eligibility. Each deposit creates incremental ownership from its own deposit target factor; a vault has no authoritative aggregate target. The live oracle quote converts ownership into ETH minting capacity; it is not the vault's ETH debt. Settlement collateral ETH held by a security pool to redeem complete sets and settle the winning outcome. Statoblast retrieves this price using OpenOracle ; see the OpenOracle integration explanation for the coordinator lifecycle. A REP withdrawal or liquidation executes immediately with a fresh price or stages behind an OpenOracle report. An accepted settlement callback attempts up to four still-active pending operations, consuming each success or terminal failure and skipping already-consumed entries. A rejected report leaves the price cache unchanged and terminally consumes its attached batch. Overflow operations remain active for later execution with a fresh price. REP/ETH Oracle Flow The coordinator is a queue plus cache. A fresh price can execute immediately. An accepted settlement callback attempts up to four still-active pending operations; a rejected report instead terminally consumes its attached batch without refreshing the cache. Overflow operations remain active for later execution with a fresh price.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["Zoltar","universes","questions","REP"],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Draft Zoltar is a generalized branching oracle and coordination layer for binary, categorical, and scalar questions. Rather than forcing a single answer when disagreement persists, it represents unresolved outcomes as explicit child universes. After a fork, REP holders can convert parent REP into a migration balance and use that balance to mint REP in one or more child universes. The design closely follows the Colored Coins model: the protocol branches state first, while applications and their users later coordinate which branch ultimately carries economic value. Zoltar is not itself a prediction market or an outcome-reporting oracle. Instead, it provides the shared infrastructure for questions, REP tokens, and universe forks. Any application can build on top of it and define its own local dispute process. If disagreement cannot be resolved locally, anyone able to commit the required REP threshold may fork an unforked universe using an eligible ended global question. The resulting child universes become the new foundations from which applications may choose to continue.","title":"Zoltar and branching truth","topic":"Protocol","weight":1},{"fragment":"timeline","heading":"Lifecycle Timeline","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar's lifecycle has six conceptual steps, from question registration through post-fork coordination. 1. Register question ZoltarQuestionData stores the question and defines its valid answer space. 2. A fork is triggered After the question end date, any address able to supply the threshold REP can call forkUniverse . Zoltar does not verify that a disagreement exists; applications decide when a fork is justified. 3. Parent universe forks Zoltar records the fork question and deterministically defines every valid child branch. 4. Child universes deploy lazily Branches exist by deterministic id first; contracts are deployed only when needed. 5. REP holders split Migration balances mint child REP into one or more selected branches. 6. Applications and users choose where to continue The protocol does not pick a winner. Users and applications decide where durable activity continues.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar has one responsibility: define and account for branching universes. It does not implement the market, collateral, or underwriting system built on top of it elsewhere in the repo. Its role is narrower and more fundamental: register questions encode valid answer spaces represent forks as child universes mint and burn child-universe REP turn migration balances into selected child REP In practice, an application can register a question, trigger a fork after that question ends, and use the child-universe structure that Zoltar defines. ZoltarQuestionData stores question state and answer encoding. Only Zoltar can mint the child-universe ReputationToken ; genesis REP is separately deployed and supplied to Zoltar. Zoltar can never mint more REP into a single universe than its parent has REP.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"Zoltar Contracts","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Zoltar : universe forks and REP splitting ZoltarQuestionData : question registry and outcome encoding ReputationToken : child-universe REP minted and burned by Zoltar ScalarOutcomes : scalar formatting and interpolation logic","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"universe-model","heading":"2. Universe Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# A universe is one branch of protocol state. The Zoltar.Universe struct stores the minimum data needed to identify that branch and connect it to its parent: forkTime : when the universe forked forkQuestionId : the question id recorded by forkUniverse and used to define child branches forkingOutcomeIndex : the outcome index represented by the universe when it is a child reputationToken : the REP token used inside that universe parentUniverseId : the parent branch Universe 0 is the genesis universe. Its network-specific REP token is supplied to the Zoltar constructor. Mainnet uses the existing REP deployment; Sepolia deterministically deploys a genesis REP token from the configured initial-holder allocations before deploying Zoltar . Child universes are identified deterministically as: childUniverseId = uint248 ( keccak256 ( abi.encode ( parentUniverseId , outcomeIndex ) ) ) Child universe ids are deterministic hashes of the parent universe and outcome index. Deterministic child ids make each branch reproducible from parent universe and outcome index alone. Child universes are deployed lazily when a forked branch is actually needed. Concrete balance example. Alice converts 100 parent-universe REP into a migration balance and mints 100 REP in the Yes child and 100 REP in the No child. This reproduces the parent REP claim across explicitly selected branches. Genesis ├── Yes: Alice holds 100 Yes-REP │ ├── Option A: Alice may later mint 100 Yes/A-REP │ └── Option B: Alice may later mint 100 Yes/B-REP └── No: Alice holds 100 No-REP Universe Tree Universe tree. A fork creates the full valid branch set; no branch is privileged by the contract. Forking parent Invalid branch Valid outcome branches Universe tree A fork creates the full valid branch set; no branch is privileged by the contract. Important distinction Zoltar defines the branch set. It does not select one canonical child universe or delete the others.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"global-question-scope","heading":"Global Question Scope","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Questions are global protocol objects in Zoltar. forkUniverse checks that the target universe exists, still has supply, has not already forked, and that the supplied question exists and has ended. It does not require the question to have been created for that universe. Applications that need a stricter universe/question relationship enforce it above Zoltar. Accepted eligibility boundary Zoltar intentionally accepts any existing ended global question. It does not require a minimum creation age, prior universe registration, or proof that the fork's disruption is smaller than its economic benefit. The fork-threshold REP commitment and configured haircut are the protocol's admission mechanism. A stricter relevance, aging, or cost-benefit rule is not a Zoltar invariant.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"3. Fork Thresholds and REP Economics","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# To trigger a Zoltar fork, the caller must commit the forkThresholdAttoRep : the parent universe's theoretical REP supply divided by the configured threshold divisor, rounded down. The REP threshold and haircut are the intended admission cost for a fork, including unnecessary forks that impose migration work on applications using that universe. Both the threshold and the haircut are intended to make unnecessary forks economically costly. Forking affects applications and users relying on that parent universe; each application chooses which child branch, if any, to continue using. forkThresholdAttoRep = ⌊ totalTheoreticalRepSupplyAttoRep forkThresholdDivisor ⌋ With the default divisor of 20 , this is approximately 5% of theoretical REP supply, subject to integer flooring. Triggering a fork removes the full fork threshold from the parent universe. `forkUniverse` removes `forkThresholdAttoRep` of parent REP and reduces the parent universe's theoretical REP supply by the same amount. Only part of that removed REP is credited toward REP in the child universe. forkBurnDivisor determines the haircut. With forkBurnDivisor = 5 : 20% is burnt: ⌊forkThresholdAttoRep / 5⌋ 80% becomes migration credit: ⌈4 × forkThresholdAttoRep / 5⌉ The constructor rejects forkBurnDivisor < 5 uncreditedForkHaircutAttoRep = ⌊ forkThresholdAttoRep forkBurnDivisor ⌋ forkInitiatorMigrationBalanceAttoRep = forkThresholdAttoRep - uncreditedForkHaircutAttoRep Threshold Deposit Split Threshold Deposit Split. The full parent-REP threshold leaves parent supply. Most is re-expressed as migratable child-REP credit; the haircut is not credited. Genesis REP cannot be burned natively, so the contract transfers it to the configured burn address. Child-universe REP is minted and burned directly by ReputationToken under Zoltar’s control. A child's theoretical supply is a maximum for REP that can be minted in that branch. It starts from the parent's pre-fork theoretical supply and subtracts only the uncredited haircut. Later REP added to a migration balance converts 1:1; only fork initiation pays this admission cost. A holder can voluntarily burn REP and reduce theoretical supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"Repeated-fork threshold decay","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Along one lineage, each fork subtracts its floored haircut from child theoretical supply. With threshold divisor 20 and burn divisor 5 , supply is close to 99% of the previous generation, subject to Solidity integer flooring. Repeated-fork economics. Each default fork leaves approximately 99% of the previous generation's theoretical REP supply, and the next fork threshold remains 5% of that declining supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"splitting","heading":"4. Child Universes and REP Splitting","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Once a universe forks, child universes can be deployed lazily through deployChild . A user can also add more REP into the migration balance with addRepToMigrationBalance . The core post-fork action is splitMigrationRep , which lets a holder mint child-universe REP for valid outcome indices. Supplying no outcome indices is accepted as a no-op at the Zoltar layer. For categorical questions, Invalid and any in-range categorical outcome are allowed, while out-of-range values are rejected. For scalar questions, only well-formed scalar encodings are allowed. The fork question defines the child-branch shape for the whole universe. A child branch may therefore be keyed by Invalid , a categorical outcome index, or a scalar encoding depending on which parent-universe question forkUniverse used for the fork. Colored Coins-style property The same migration balance can mint child REP in each selected child. Later value concentration determines which branch or branches matters economically. At the implementation level, splitMigrationRep validates each selected outcome against the parent universe's fork question, lazily deploys any missing child universe, and records how much of the caller's migration balance has already been minted into each child. A caller cannot mint more into one child than the source migration balance available to that caller, but the same source balance can be reproduced into multiple valid children.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"security","heading":"5. Assumptions and Security Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar is a Colored Coins -style system, so its security argument depends on user behavior and value concentration rather than on the contract being able to identify one objectively correct branch onchain. users can choose which child universe (or child universes) to continue using after a fork users prefer to continue in the universes they regard as truthful the protocol itself does not know which universe is truthful and treats all valid branches symmetrically most durable economic activity concentrates in the branch that users expect other users to keep using dishonest or abandoned branches may continue to exist, but are assumed to retain little long-term value compared with the branch that market participants keep coordinating around","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"6. Questions and Outcome Encoding","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# ZoltarQuestionData.QuestionData stores title, description, start and end time, scalar metadata such as numTicks , displayValueMin , displayValueMax , and answerUnit . Question ids are deterministic hashes of the question data. For categorical questions, that hash path also includes the sorted categorical outcome options. For scalar questions, there are no categorical labels to include, so the id is determined from the scalar question fields alone. Every call to createQuestion also requires endTime >= startTime . The contract rejects any question whose end time is earlier than its start time before it reaches scalar or categorical validation.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Categorical Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Categorical questions store sorted outcome labels. The implementation requires labels to be non-empty and strictly ordered by hash: each label's keccak256(abi.encode(label)) value must be lower than the previous label's hash, so callers provide labels in descending hash order. The contract stores the labels in outcomeLabels[questionId] . Any number of categorical outcomes can exist at the Zoltar level as long as those conditions hold.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Scalar Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Scalar questions store no categorical labels. Instead, numTicks , displayValueMin , displayValueMax , and answerUnit define the answer space. At creation time, scalar questions must satisfy numTicks > 0 and displayValueMax > displayValueMin . Onchain, each scalar answer is encoded into a single uint256 that packs: the highest bit as an invalid flag a 120-bit first payout numerator a 120-bit second payout numerator Packed Scalar Answer Packed Scalar Answer. A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks. Namespace bit First payout Second payout Packed Scalar Answer A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks . The all-zero encoding is the canonical Invalid answer for scalar questions. For a valid scalar answer, the two payout numerators must sum exactly to numTicks : firstPayoutNumerator + secondPayoutNumerator = numTicks A valid scalar answer must allocate all ticks across the two payout numerators. The all-zero word is the only canonical scalar Invalid value. Any other word with the highest bit clear is malformed, and any word with a nonzero reserved bit in 254...240 is malformed even if the payout fields would otherwise decode cleanly. At the helper and UI level, a scalar tick index named tickIndex is encoded as: firstPayoutNumerator = numTicks - tickIndex secondPayoutNumerator = tickIndex highest bit = 1 ( numTicks - tickIndex , tickIndex ) The helper encodes a tick as left and right payout numerators. ScalarOutcomes interprets secondPayoutNumerator as the position along the scalar range: displayedAtomic = displayValueMin + ⌊ secondPayoutNumerator ⋅ ( displayValueMax - displayValueMin ) numTicks ⌋ The contract interpolates the scalar answer with mulDiv , so uneven divisions round down before the value is formatted. displayValueMin and displayValueMax are stored as 18-decimal fixed-point display bounds. Formatting divides the atomic result by 1e18 and trims trailing zeroes. For example, with displayValueMin = 0 , displayValueMax = 10e18 , numTicks = 6 , and secondPayoutNumerator = 1 , the atomic value is ⌊10e18 / 6⌋ , which displays as 1.666666666666666666 rather than an unrounded real number.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"question-types","heading":"7. Question Types Supported by Zoltar","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# At the Zoltar layer, market type support comes from how questions and outcomes are encoded in ZoltarQuestionData . categorical questions, implemented as an ordered array of non-empty outcome labels binary questions, implemented as categorical questions scalar questions, implemented as a tick-based numeric range with no categorical labels","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"invalid","heading":"8. Invalid vs Malformed","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar distinguishes Invalid answers from Malformed answers. Malformed answers are rejected, while Invalid remains a valid branch and a valid final outcome. Term Meaning Effect Invalid A legitimate resolution state. Can be a valid branch and final outcome. Malformed A submitted outcome index or scalar encoding that does not fit the question’s answer space. Rejected during child-universe REP splitting and fork-aware asset branching.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["contracts","architecture","calls","assets"],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Documentation index Mental model: actors enter through pools, oracle coordination, Zoltar universes, and child repair; custody boundaries explain the call graph. This map connects the primary deployed Zoltar registry and Statoblast market contracts. It emphasizes contract-to-contract calls and asset movement, not every public user entrypoint. Factory-only deployment workers, delegate modules, event emitters, and compatibility contracts are summarized separately so the runtime path stays legible. Related reading: the generated transaction reference for exact caller and prerequisite rules, the Statoblast whitepaper for lifecycle context, and the invariant catalog for cross-contract safety properties.","title":"Contract architecture","topic":"Architecture","weight":1},{"fragment":"conceptual-model","heading":"Conceptual Model Before the Graph","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Statoblast separates identity, market accounting, price-sensitive execution, dispute escrow, and fork repair: ZoltarQuestionData stores question state and answer encoding; Zoltar manages universes, forks, and REP branches. A security pool tracks market collateral, vault accounting, open interest, and lifecycle state. The share token tracks outcome-share balances. The price coordinator stages operations that require a fresh REP/ETH price. The escalation game escrows reporting REP and tracks local dispute state. The pool forker creates and repairs child pools after a fork. One complete-set path is: a trader calls SecurityPool , the pool accepts ETH collateral, and the pool calls ShareToken to mint one share for every outcome. The event stream records the supply and accounting changes. The detailed graph below shows primary protocol interactions; supporting factories, delegates, emitters, callbacks, and compatibility contracts are summarized rather than expanded.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"overview","heading":"Primary Interaction Flow","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Arrows point from the contract initiating an interaction to the contract receiving it. The map repeats a contract when it participates in more than one phase, separating construction-time wiring, recurring runtime calls, and fork repair into three readable panels. Users and keepers enter through several of these contracts, but are omitted so the plot can focus on contract-to-contract boundaries. Contract interactions are separated into three phases. During deployment, the pool factory validates question and universe data before deploying the pool, share token, and price coordinator. During runtime, the pool manages claims and escalation escrow while guarded actions travel through the price coordinator and OpenOracle. During a fork, the share token enters the pool forker, which snapshots escalation, migrates REP through a proxy, creates and migrates child pools, and repairs backing through the truth auction. Contract Interaction Map Each panel is a distinct protocol phase; repeated contracts preserve local reading order and prevent unrelated lifecycle edges from crossing. Blue nodes are registries or oracle infrastructure, green nodes hold market state and claims, red marks local resolution, and gold marks deployment or fork coordination. Short edge labels state the action; the table below supplies its exact contract-level meaning.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"edges","heading":"What Each Arrow Means","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Initiator Receiver Phase Interaction SecurityPoolFactory ZoltarQuestionData Deployment Confirms that the question exists and is the required Yes/No categorical shape before reserving the canonical pool identity. SecurityPoolFactory Zoltar Deployment Checks the universe and resolves its REP token for the new pool and price coordinator. SecurityPoolFactory SecurityPool Deployment Deploys and records the canonical origin pool. Forker-requested child deployment copies the lineage and fork state. SecurityPoolFactory ShareToken Deployment Deploys the origin lineage token and authorizes the canonical pool; child pools reuse that same token. SecurityPoolFactory OpenOraclePriceCoordinator Deployment Deploys the REP/WETH price coordinator and wires it to the newly created pool. Zoltar ReputationToken Universe lifecycle Deploys child-universe REP, sets its theoretical supply, and exclusively mints or burns that child REP during migration and voluntary burns. SecurityPool ShareToken Market runtime Mints complete sets, burns complete sets or winning shares, and authorizes canonical child pools to continue the same lineage claims. SecurityPool EscalationGame Resolution Deploys the local game on first use and transfers vault REP into outcome-specific escrow. Settlement returns winning value or carries proofs across a fork. SecurityPool OpenOraclePriceCoordinator Risk operations Reads the coordinator's last valid REP/ETH price. Vault owners and liquidation operators stage withdrawals and liquidations directly on the coordinator before restricted pool execution; target-health deposits create capacity ownership directly at the pool. OpenOraclePriceCoordinator OpenOracle Price discovery Funds a REP/WETH report and later withdraws the settled reporter balances that it validates and returns to the report sponsor. OpenOracle OpenOraclePriceCoordinator Price settlement Calls openOracleCallback after settlement so the coordinator can validate the final report, update its cached price, and attempt bounded staged-operation execution. OpenOraclePriceCoordinator SecurityPool Risk execution After price and snapshot checks, calls the pool-only liquidation or REP-withdrawal entrypoint. Those operations update capacity ownership internally when REP backing or liquidation ownership moves. ShareToken SecurityPoolForker Share migration Initiates a source-pool fork when needed and asks the forker to create a missing canonical child before migrating a holder's shares. SecurityPoolForker EscalationGame Fork snapshot Reads carry peaks, roots, outcome balances, timing, and fork parameters so unresolved escalation state can continue in child pools. SecurityPoolForker SecurityPoolMigrationProxy Fork migration Deploys and controls the pool-specific adapter, transfers parent REP to it, and instructs it to lock, fork, split, and sweep child REP. SecurityPoolMigrationProxy Zoltar Fork migration Acts as the literal Zoltar caller whose stable address holds the migration ledger balance while parent REP is locked and split into child-universe REP. SecurityPoolForker SecurityPoolFactory Fork migration Requests canonical child-pool deployment for selected child universes. SecurityPoolForker SecurityPool Fork migration Copies or recomputes financial state, migrates vaults and collateral, and activates the selected child lifecycle. SecurityPoolForker UniformPriceDualCapBatchAuction Backing repair Starts, finalizes, and settles the child truth auction when migrated REP and inherited ETH collateral do not arrive in the required proportions.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"paths","heading":"Four Useful Reading Paths","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Deploy a market: ZoltarQuestionData and Zoltar provide the question and universe context; SecurityPoolFactory creates the pool, share token, and price coordinator. Trade and resolve locally: SecurityPool accepts ETH collateral and mints one share for every outcome through ShareToken , then escrows reporting REP in EscalationGame . Reprice and liquidate: OpenOraclePriceCoordinator obtains a report from OpenOracle , then calls a restricted execution method on SecurityPool . Continue after a fork: SecurityPoolForker migrates REP through Zoltar , asks SecurityPoolFactory for child pools, transfers the inherited state, and uses UniformPriceDualCapBatchAuction when backing must be repaired.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"supporting-contracts","heading":"Supporting Contracts Not Expanded in the Plot","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The diagram groups construction helpers and delegate modules behind the deployed component whose behavior they implement: ShareTokenFactory , EscalationGameFactory , PriceOracleManagerAndOperatorQueuerFactory , UniformPriceDualCapBatchAuctionFactory , and SecurityPoolDeployer construct the displayed runtime contracts. EscalationGameDepositDelegate , EscalationGameClaimDelegate , and the SecurityPoolForker* delegate modules execute against their owning contract's storage; they are implementation boundaries, not independent user-facing protocol hubs. SecurityPoolMigrationProxy isolates a pool's Zoltar migration balance, while SecurityPoolEventEmitter preserves pool-address event attribution under delegated fork work. WETH9 , Multicall3 , and the imported OpenOracle implementation are compatibility or external boundaries; only OpenOracle's protocol-facing relationship is shown.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"sources","heading":"Source Contracts","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"ZoltarQuestionData.sol Zoltar.sol and ReputationToken.sol SecurityPoolFactory.sol SecurityPool.sol and ShareToken.sol EscalationGame.sol and SecurityPoolForker.sol OpenOraclePriceCoordinator.sol and OpenOracle.sol UniformPriceDualCapBatchAuction.sol","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"actors-and-boundaries","heading":"Actors, custody, and one operation","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The protocol consists of several participants with distinct responsibilities. Market creators configure security pools, traders mint and hold complete sets and outcome shares, vault owners provide REP backing, liquidators repair under-collateralized vaults, auction bidders recapitalize child pools after forks, keepers execute permissionless maintenance operations, and indexers reconstruct protocol state from emitted events. Each contract handles a specific part of the protocol. Security pools hold user funds and manage market state. The Oracle Coordinator runs price-sensitive operations that need a REP/ETH price. Zoltar manages universes, questions, REP, and forks. An operation may involve several contracts, but that does not move every asset or piece of state between them. For example, when a trader mints a complete set, they send ETH to the security pool. The pool accepts the deposit, mints one outcome share for every possible outcome, and updates its accounting. Each contract changes only the state it manages, while emitted events let external indexers reconstruct the operation. The deployment graph above illustrates the primary contract interactions in this release. Supporting factories, delegates, callbacks, emitters, and compatibility contracts are omitted for clarity. Contract addresses and call relationships are deployment details, while the custody and responsibility boundaries described above remain stable across deployments.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["EscalationGame","modules","authority","accounting"],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The Escalation Game's deposit, timing, outcome-selection, and continuation rules fit into the wider prediction-market lifecycle described in the Statoblast white paper. When a Statoblast market ends, the security pool sends a valid deposit through its escalation-deposit delegate. The deposit must pass the pool's vault-backing and lifecycle checks and usually meet the configured starting bond. A smaller deposit is allowed only when it fills an outcome to the non-decision threshold . The first deposit does not resolve the market by itself. A deposit moves the local deadline only when it raises the median of the three outcome balances. A strict leader identifies the local result, but the pool can finalize it only after the deadline. If competing outcomes reach the non-decision threshold, the pool follows the fork path. Lifecycle: tentative claim → conflicting deposit → accepted amount and new deadline → local settlement or non-decision → child continuation after a fork → proof-backed claims.","title":"Escalation game architecture","topic":"Architecture","weight":1},{"fragment":"escalation","heading":"Escalation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"A vault owner with enough backing can add REP to an outcome with a valid deposit that meets the configured start bond. The deposit adds to that outcome's cumulative balance; it does not have to exceed the leader. The deadline moves only when the deposit raises the median outcome balance. Separately, the cumulative binding-capital threshold rises over time: Attrition cost is zero on days 0–2, equals the start bond on day 3, and reaches the non-decision threshold on day 52. Required support threshold A(d) = 0 for 0 ≤ d < 3; S for d = 3; S × exp(ln(T / S) × (d − 3) / 49) for 3 < d < 52; T for d ≥ 52 Here S is the configured start bond and T is the non-decision threshold. The rendered chart uses the contract's fixed-point arithmetic; this exponential expression is its readable idealization. The contract waits three days after start() before the escalation clock begins. The chart labels day 0 (game start), day 3 (activation), and day 52 (the end of the seven-week escalation interval). Try a simplified escalation round This simulator uses the contract model: a three-day activation delay, a 49-day escalation interval, and the fixed-point attrition curve. Change the configuration, then choose an outcome and requested deposit to see clipping, tie adjustment, acceptance, or rejection. Start bond 1 REP Non-decision threshold 10 REP Current Yes balance 1 REP Current No balance 1 REP Current Invalid balance 0 REP Deposit outcome Yes No Invalid Requested deposit 1 REP Days since start 0 days Configured start bond 1 REP Configured threshold 10 REP Leading outcome Yes / No Median balance 1 REP Deposit before → after Yes 1 / 2 REP; No 1 / 1 REP; Invalid 0 / 0 REP Deposit result accepted: 1 REP Activation 3 days Scheduled deadline (before → after) day 3.0 → day 3.0 Contract-model state deadline pending","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"The game end","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The game can end two ways The required escalation game increased high enough that only one side was able to keep up with it: That side wins and Statoblast finalizes on that outcome Two sides have reached the maximal deposit amount: Statoblast triggers a fork After the game ends, the payout uses binding capital , the safety boundary , and the reward-eligible cap . A winning deposit returns its principal and may receive a bonus. Any excess above the reward cap returns principal without a bonus. The game burns the winning-deposit haircut ; forked payouts can also be scaled by the fork threshold. Exact example. Let binding capital be 10 REP , so the reward-eligible cap is 10 + 10 / 2 = 15 REP . The reward pool is 10 × 3 / 5 = 6 REP , and the haircut pool is 10 × 2 / 5 = 4 REP . If the winning outcome has 15 REP and a deposit contributes 5 REP within the eligible range: Principal returned: 5 REP . Bonus: 5 × 6 / 15 = 2 REP . Winning payout: 5 + 2 = 7 REP . Haircut burned: 5 × 4 / 15 = 1.333333333333333333 REP at attoREP precision. At local settlement, a deposit position above 15 REP returns its principal but receives no bonus. If the fork threshold is 8 REP instead of the 10 REP non-decision threshold, the whole local withdrawal is scaled: 7 × 8 / 10 = 5.6 REP , rounded down to attoREP. Fork scaling can therefore reduce the final transfer below principal.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"glossary","heading":"Glossary","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"Binding capital The median REP balance across Invalid, Yes, and No. It is the amount the winning result must support and the value that sets the scheduled deadline. Non-decision threshold The configured REP balance at which two outcomes have enough support to stop local resolution and use the fork path. Safety boundary The interval above binding capital where a winning deposit can still earn a bonus. In this model it is (binding capital, reward-eligible cap] . Reward-eligible cap Binding capital plus half of binding capital. Only the portion of winning deposits up to this cap participates in the bonus calculation. Principal The REP originally deposited. A winning deposit returns its principal before any bonus is added. Haircut The REP amount calculated for burning from the reward-eligible portion of a winning deposit. It is separate from the payout amount.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"Escalation game continuation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"In AugurV2, when a fork happens, it cancels all currently running escalation games, and the players are refunded. This opens an attack vector for the oracle, discussed more in Zoltar Forks Should Not Cancel Escalation Games . The fix to the issue is to fork escalation games. This is called continuation. Escalation game continuation uses Merkle Mountain Range carry proofs and nullifier roots to migrate user game deposits, without requiring expensive data copying efforts onchain.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["liquidation","capacity","operator","receiver vault","approval","reservation","bad debt"],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Liquidation moves live ETH-denominated open interest and the corresponding REP-denominated capacity ownership away from an unhealthy vault. The receiver accepts the liability and receives the funded REP award. A separate operator may submit the transaction and pay its gas and oracle costs.","title":"Capacity and delegated liquidation","topic":"Economics","weight":1},{"fragment":"position-model","heading":"Three Distinct Roles","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The operator requests and stages the liquidation, paying that transaction's gas and any oracle cost. The receiver vault accepts moved security-bond debt and receives the capacity ownership and REP award. The target vault is the unhealthy vault whose position is reduced. If the liquidation remains staged, any account may execute it later and pay the execution transaction's gas. That permissionless executor does not replace the staged operator named by a delegated approval. The receiver and target must differ. The operator may be either one, but identity alone is not a health or Sybil-resistance boundary. For the backward-compatible path, omitting a delegated receiver makes the operator the receiver and requires no signed approval. The operator receives no liquidation ownership or REP merely for submitting the transaction. The first release assumes the operator and receiver coordinate compensation externally; the protocol neither creates a keeper marketplace nor takes a fee from the award.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"capacity-and-health","heading":"Capacity and Live Health","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A vault deposit creates incremental REP-denominated capacity ownership from the deposited REP and that deposit's selected target factor. Deposits with different targets combine, so the latest deposit target is not a vault-wide health factor. The pool totals capacity ownership in constant time. The live REP/ETH oracle price and the pool Statoblast security multiplier convert that total into current ETH minting capacity. A REP price change therefore changes capacity without rewriting vaults, and it never deletes already-open interest. Vault health uses the vault's live REP backing, locally attributed dispute-staked REP, proportional live open interest, capacity ownership, the settled REP/ETH price, and the pool multiplier. The ordinary associated-REP branch counts pool-held and locally staked REP. The free-REP branch counts only pool-held REP because locked claims may lose. Required REP is rounded upward. A delegated approval may require a higher post-liquidation health factor. 10,000 means exactly the protocol minimum. The signed factor is applied to both backing branches and is checked again against live post-liquidation state at execution; a queue-time preview is never a guarantee.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"punitive-liquidation","heading":"Funded Transfer and Bad Debt","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The requested amount is ETH-denominated debt. Execution first caps a nominal quote by the target's live open interest and by the debt whose complete 5% REP bonus the target can fund. Capacity ownership is selected from that quote and rounded downward. The debt actually moved is then the exact increase in the receiver's live, upward-rounded open-interest allocation, which cannot exceed the nominal quote. On a delegated route, the coordinator additionally requires it not to exceed the staged approval reservation; the self-receiving path has no approval reservation. A positive quote that produces no receiver debt reverts. The target and receiver fee positions are checkpointed before their ownership changes; the operator is not checkpointed unless it is also one of those vaults. Delegated Liquidation Transfer Floor-rounded proportional capacity ownership and ceiling-rounded REP backing units leave the target. The authorized receiver incurs the exact reported debt increase and receives the ownership and backing units; target claims, fees, surplus, and unmatched ownership remain. Only a full-target request records residual target debt as bad debt. Liquidation accounting The operator submits the transaction, while the authorized receiver accepts the funded liability and receives the award. grossRepAwardAttoRep = ⌈ debtMoved ⋅ repPerEthPrice ⋅ 10,500 / ( PRICE_PRECISION ⋅ 10,000 ) ⌉ The gross award is converted into REP backing units with upward rounding. Because backing units are a proportional claim on the pool's current REP balance, the credited units can convert back to more current REP than grossRepAwardAttoRep ; the excess is less than the current REP value of one backing unit. On a full-target request, target-local bad debt is the target's live debt minus the receiver's exact settled increase. The residual may include debt excluded by the REP-award funding cap and a small amount left by integer ownership and open-interest allocation rounding. It is not an ETH collateral-shortfall test. Capacity ownership is conserved between the target and receiver; any ownership not transferred remains with the target. After a funded transfer, the receiver's resulting debt must meet the configured debt minimum and its REP backing must meet the configured vault REP minimum. A transfer smaller than the debt floor can still succeed when the receiver's existing position keeps its resulting debt above the floor. The target's resulting debt must be zero or meet the debt floor; when target debt remains, its REP backing must also meet the vault REP floor. These receiver or target dust checks revert instead of converting otherwise funded debt into bad debt.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"receiver-approvals","heading":"Bounded Receiver Approval","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A delegated route needs a pool-specific approval for the exact receiver and operator. It may name one target or use the zero-address wildcard for any target in that pool. Each approval defines a cumulative ETH debt limit, a per-liquidation limit, a minimum post-liquidation health factor, an activation time, an expiration time, and a nonce. The receiver may install an approval directly or sign EIP-712 typed data. The domain binds the stable name and version, chain ID, and approval registry; the typed message explicitly binds the security pool. EOA signatures use ECDSA and contract-wallet signatures use ERC-1271. A contract-wallet signature is validated when installed and becomes explicit onchain state rather than an opaque signature retained until execution. Each receiver-scoped nonce can install only one approval, and replaying it is rejected. The receiver can revoke unused approval capacity or advance its nonce floor to invalidate older nonces. Both actions prevent new reservations without cancelling reservations already attached to staged operations. Approval state exposes available, pending reserved, and permanently consumed amounts so indexers can reconstruct every transition.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"queue-semantics","heading":"Reservation and Queued Execution","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The coordinator reserves approval quota when it stages a liquidation. The reservation is no greater than the requested debt, the snapshotted target debt, the approval's remaining cumulative quota, or its per-operation limit. The approval must remain valid through the operation's latest possible execution time. Successful execution permanently consumes exactly the debt moved and releases unused reservation. A bad-debt-only result consumes no receiver quota. Expiration and every terminal failure release the full reservation, including stale target snapshots, target rescue, price-distance failure, inactive or forked pools, caught pool reverts, panics, unknown failures, and pending-report recovery. Cleanup is permissionless and recognizes expiration before requiring a valid oracle price. A terminal operation cannot release or consume twice. The coordinator snapshots target backing and ownership to prevent a stale quote from becoming a keeper windfall, then uses the settled price and live state at execution. Receiver state changes after staging can therefore cause a safe failure and reservation release.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"fork-behavior","heading":"Fork Isolation","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Approvals and reservations belong to one pool and its coordinator. They are not copied into child pools or universes. A receiver approving liabilities in one universe has not approved them in a child. Pending parent operations remain permissionlessly cleanable after the parent becomes unable to execute, releasing their reservations.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["fees","markets","vaults","accounting"],"path":"explanation/fees.html","sectionTitle":"Explanation","summary":"Understand fee accounting across markets, vaults, and protocol operations.","text":"Vault holders have a lot responsibilities, they need to maintain enough REP to cover their commitments for open interest holders, they need to participate in escalation games, fork migrations etc. The reward for this service is fees generated from open interest. The security pool sets a side fees for vault holders to claim when they like. Earned fees are not ever taken away from the vault holders. REP becomes valuable only when its used in the ecosystem. Complete-set collateral gradually decays into an unallocated fee reserve. Fee-eligible vaults accrue fees in proportion to their assigned capacity ownership. The retention rate falls as utilization rises, so security becomes more expensive as open interest consumes underwriting slack. Retention follows a piecewise heuristic. It starts high, declines linearly until 80% utilization, and then stays at the minimum retention rate. The intent is to charge more aggressively for security as utilization rises and underwriting slack falls. At zero utilization, the constants imply roughly a 10% yearly fee. Once utilization reaches the 80% dip, the annualized fee is roughly 50% and then stays flat because the on-chain retention rate is already pinned to its minimum. The UI annualizes the rate as annualFee = 1 - (retentionRate / PRICE_PRECISION)^SECONDS_PER_YEAR . Retention and utilization curve. The annualized fee rises from roughly 10% at zero utilization to roughly 50% at 80% utilization, then remains flat because the retention rate has reached its minimum. Piecewise retention formula The 80% rule marks the point where the minimum per-second retention rate begins to apply. Fee accrual is lazy: state-changing pool operations checkpoint the global fee index, and vault operations checkpoint each vault against that index. Fractional amounts remain in explicit remainders until they accumulate into whole attoETH, so callers do not need to iterate over every vault when time advances. Fee accrual is time-clamped. The pool extracts collateral into fees only until the question end time while the universe remains unforked; after the universe forks, the fee accumulator is instead clamped to the fork time. Retention-rate updates no-op outside Operational mode or when the recalculated rate is unchanged. While operational, utilization is tracked collateral divided by live ETH minting capacity, and that capacity uses total capacity ownership. Sold truth-auction capacity enters both total ownership and the fee denominator when the auction finalizes, so it provides minting headroom, affects utilization, and starts earning fees before the winning bidder claims it. Claiming the allocation does not change live capacity or the retention curve. A child pool begins a new fee epoch when migration and any truth auction finalize. Migrated capacity and sold auction capacity enter the fee denominator at that point. When a winning bid is claimed later, the pool assigns that ownership's fees from the finalization index through the current index to the winning vault, combining fractional carry with the vault's existing remainder. Whole fees move from the pool reserve into totalClaimableVaultFeesAttoEth ; this keeps aggregate claimable fees equal to redeemable vault balances. The totalAccruedFeesAttoEth() view adds those assigned claimable fees to the unallocated reserve when callers need to reconcile all fee-adjusted collateral. A fork permanently closes the parent's fee epoch. The pool tracks how much eligible capacity ownership still has not reconciled the final index, including sold truth-auction ownership awaiting claim. Auction claims preserve aggregate fees while assigning each winner's share. After every eligible vault syncs and every auction allocation is claimed, a subsequent permissionless vault fee checkpoint returns any whole reserve attoETH that no vault can individually redeem to complete-set collateral. Until that separate reconciliation, the reserve remains protected by totalAccruedFeesAttoEth() . REP deposits mint proportional REP backing units, settlement collateral decay increments the fee index, and vault capacity ownerships claim fees from that index. Pool Accounting REP backing units and fee accounting use separate proportional ledgers: REP backing units track vault REP backing, which is not automatically withdrawable REP, while the fee index tracks decayed ETH settlement collateral owed to vaults. attoRepToBackingUnits ( attoRepAmount ) = attoRepAmount ⋅ PRICE_PRECISION if the ledger or pool-held REP balance is empty = ⌊ attoRepAmount ⋅ totalRepBackingUnits totalPoolHeldRepBalanceAttoRep ⌋ otherwise backingUnitsToAttoRep ( repBackingUnits ) = 0 if the backing ledger is empty = ⌊ repBackingUnits ⋅ totalPoolHeldRepBalanceAttoRep totalRepBackingUnits ⌋ otherwise The bootstrap branch establishes PRICE_PRECISION backing units per attoREP. Once both totals are nonzero, each conversion floors integer division exactly as the contract does.","title":"Protocol fees","topic":"Economics","weight":1},{"fragment":"","heading":"","keywords":["truth auction","clearing","settlement","repair"],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When a pool's migration deadline has passed, if a child pool lacks the ETH needed to fully collateralize its open interest, the child may start a truth auction. The fork may have been triggered either by a direct Zoltar fork or by the Statoblast Escalation Game. Because collateral follows migration rather than being copied in full, a child pool retains its obligations while lacking enough ETH to reopen cleanly. The truth auction sells child-universe REP to raise as much repair ETH as demand supports. A truth auction does not determine which outcome is true. It sells REP belonging to one already-defined child universe to repair that child pool's collateral. The auction has an ETH raise target and a cap on how much child-universe REP it may sell. That cap depends on migrated and dispute-staked REP. Statoblast writes winning claims back into vault state rather than paying simple token transfers, so bidders buy into the child pool and its matching capacity ownership instead of merely receiving REP.","title":"Truth auction economics","topic":"Economics","weight":1},{"fragment":"lifecycle","heading":"Operational Lifecycle","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Migration deadline passes; the child may start the auction later Bidding Bidding ends and finalization occurs one week after the auction actually starts Auction Lifecycle. After migration closes, startTruthAuction either activates the child immediately when no sale is needed or opens bidding. During bidding, once demand establishes a clearing tick, bids below it may refund before finalization. Finalization later activates the child; paged calls then settle winning and partial bids plus any remaining refunds. Auction Lifecycle The start transition activates a child immediately when no sale is needed. On the repair branch, losing bids below an established clearing tick may refund during bidding; finalization activates the child, and later calls settle winning and partial bids plus remaining refunds. REP doesn't leave the system. Finalization accounts the ETH actually raised and rejects contribution-only ETH, so bidder settlement never depends on a donor.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Starting the auction","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The start call is permissionless but not automatic. It is valid strictly after the eight-week migration deadline and records the actual auction start time. That timestamp starts the one-week bidding window.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Bidders submit bids at discrete price ticks , where each tick represents a maximum price in ETH per REP that the bidder is willing to pay. Tick 0 corresponds to a price of 1 ETH/REP . Each increase of one tick multiplies the price by approximately 1.0001 , while each decrease divides it by the same factor. The price represented by tick tickIndex is therefore approximately 1.0001 tickIndex ETH/REP . Uniswap uses a similar tick system. Losing bids can be refunded before finalization once current demand is enough to find a clearing tick. Only ticks below that clearing tick can be withdrawn through the pre-finalization refund path; binding or potentially winning bids stay in the auction. Once a clearing tick is found, it cannot be lowered, so currently losing bids remain losing bids.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding end","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When bidding ends, anyone may finalize the auction. Finalization clears the batch auction, sends the raised ETH into the respective security pool, and computes the accounting rates used to settle bidders. The child activates with legitimate migration settlement collateral plus retained bid ETH, even when that total is below the original fork snapshot. Full repair is not guaranteed, and the remaining impairment is borne by the affected child's open-interest holders. At finalization, unclaimed auction backing, capacity, and bad debt enter an explicit, nonwithdrawable unassigned position. When the auction purchases REP, the position also owns fees from finalization onward. getUnassignedPosition exposes its backing, capacity, and debt even when the auction purchases no REP; SecurityPoolUtils.getUnassignedPositionFeeAccounting exposes its finalization fee index and any checkpointed fee entitlement. Its open interest is the ceiling-rounded capacity share of child settlement collateral minus unassigned bad debt, floored at zero. A complete-set mint must leave both the pool and this position healthy. A later winner claim transfers the bid's proportional backing units, capacity ownership, bad debt, and accrued fees from that position into child-pool vault accounting, rather than transferring REP tokens directly. Capacity ownership and bad debt are allocated from each bid's fixed cumulative ETH position, so division dust and each winner's balance are independent of claim order. The forker tracks collateral received through migration and the auction separately from the child contract's raw ETH balance. ETH forced into the child does not count toward the repair target. Finalization activates the child with migrated collateral plus accepted auction ETH and rejects nonzero finalizer contribution ETH.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"How Clearing Works","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The clearing process walks demand from the highest price downward. A bid above the clearing tick wins in full, a bid below it loses in full, and bids at the clearing tick share the last fill. In the uniform-clearing branch that last fill is First in First Out within the tick, so earlier same-tick bids are consumed before later ones. Higher ticks represent higher bid prices and therefore receive priority during auction clearing. The clearing algorithm matches bids starting from the highest tick and works downward until all available REP has been allocated. The auction has two caps ETH raise cap. The auction only wants to raise ETH required to make open interest holders whole REP selling Cap. The auction can at most sell all the rep it has As soon as cumulative demand is enough to hit either cap at some price, that price becomes the clearing tick and all winning bids settle from it at one price. That is a valid uniform clearing even when the REP cap binds before the ETH repair target is fully met.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"Underfunded Clearing","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When demand never reaches a full clearing price, the auction switches into the underfunded branch. Finalization computes underfundedThreshold , stores the lowest tick whose price reaches that qualification threshold as clearingTick . Only bids at or above the cap-implied qualification threshold are retained; lower ticks refund. Qualifying bidders collectively purchase maxRepBeingSold for underfundedWinningEth , so the common effective price is their retained ETH divided by the REP cap. That effective price can be lower than the qualification threshold; the threshold is not an execution-price floor. If no ETH qualifies, every bid refunds. Sold REP is allocated between them proportionally with integer floors: “them” means the pool-held and unresolved dispute-staked REP buckets. The dispute-staked bucket receives ⌊repPurchased * disputeStakedRepBefore / combinedRepBefore⌋ , and the pool-held bucket receives the complementary remainder. This bucket split happens before REP is allocated among qualifying bidders, and each bucket keeps separate backing and retention accounting. underfundedThreshold = ⌈ attoEthRaiseCap ⋅ PRICE_PRECISION maxAttoRepBeingSold ⌉ PRICE_PRECISION = 1e18 . The ceiling tick is the bid-eligibility boundary, not an execution-price floor. When the auction finalizes with purchased REP, the sold capacity ownership enters feeEligibleCapacityOwnershipAttoRep . It affects retention utilization and accrues fees into the unallocated reserve before a winner claims. After a zero-purchase auction, the system-owned remainder stays visible in the unassigned position but remains outside fee eligibility because no bidder has an economic fee entitlement. Bounded child-local REP backing units. Vault migration first converts each parent claim into REP, then credits that amount as child-local backing units. It never copies a recursively scaled parent unit balance. Every positive migrated amount reserves at least one attoREP of the pool's own inventory. If unresolved escalation REP is auctioned with the pool-held REP, the forker scales that reserve across the combined inventory and rounds upward. This guarantees that proportional allocation leaves enough pool-held REP—not merely game REP—to back the migrated claim after another owner redeems. Zero pool-incumbent case. If proportional allocation leaves no REP for the migrated pool owners, finalization uses the fixed PRICE_PRECISION auction rate. This includes a zero-migration mixed auction whose unsold unit belongs entirely to the escalation game. Positive pool-incumbent case. Here poolRepAfter is the REP held by the pool after the auction and escalation haircut, while incumbentRepAfter is the portion of the pre-auction pool-held REP left for migrated owners by proportional allocation. The auction rate is ⌈poolRepAfter / incumbentRepAfter⌉ . The resulting denominator is a multiple of pool-held REP, so winner backing units round-trip exactly through backingUnitsToAttoRep . Both cases use full-precision multiplication so every supported conversion remains representable.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"avl-tree","heading":"AVL tree","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The auction indexes bids in an AVL tree keyed by tick. The valid tick range is finite: [-524288, 524288] , or 1,048,577 possible price levels. The tree stores only submitted ticks and keeps each subtree's demand summary. Because a full tree over the range has maximum height 28 , finalize() follows bounded paths and prunes subtrees instead of looping over every possible price level.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"interactive-example","heading":"Interactive Clearing Example","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The slider example shows the uniform-clearing path, the underfunded path, and how the same bid ladder reacts when the ETH cap, REP cap, and bid sizes change. Try a simple auction clearing run The simplified calculator visualizes both finalized branches with three fixed price levels and real-number arithmetic. The clearing section specifies the contract's exact tick and cumulative-floor behavior. ETH raise cap 12 ETH REP inventory 4 REP Alice bid near 5 ETH/REP 3 ETH Bob bid near 4 ETH/REP 4 ETH Carol bid near 3 ETH/REP 6 ETH High-price bids are considered first. If cumulative demand reaches the ETH raise cap or REP sale cap, the first binding tick sets normal uniform clearing. If neither cap is reached, finalization selects bids at or above the cap-implied qualification tick and allocates the complete REP sale cap among them at one effective price. With no qualifying bids, every bid refunds. Interactive Demand Curve The stepped curve accumulates bids from the highest limit price to the lowest. The vertical rule is available REP, the horizontal rule is the current clearing or qualification price, and colored bid points distinguish accepted demand from demand below the boundary. The chart updates from the auction controls directly above it. Mode uniform clearing near 3 ETH/REP Binding condition both caps ETH retained 12 ETH Winning ETH kept not underfunded Threshold not underfunded Approximate Alice REP 1.00 REP Approximate Bob REP 1.33 REP Approximate Carol REP 1.67 REP Total REP allocated 4 REP Refunds 1.00 ETH","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"three-auction-outcomes","heading":"Three outcomes to keep separate","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"No auction is required when migrated REP already repairs the child pool. A funded auction means a clearing cap bound, not necessarily full ETH repair: compare accepted ETH with the repair cap. An escalation-allocation haircut can still apply when purchased REP is allocated to positive dispute-staked REP. An underfunded auction clears qualifying demand at or above the reserve threshold and refunds lower bids; it records the ETH shortfall, may apply the conditional escalation-allocation haircut, and cannot create missing ETH or duplicate REP.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["OpenOracle","price","coordinator","callback"],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle is a source for REP/ETH price for Statoblast. Open oracle supplies a fresh REP/ETH price for solvency-sensitive operations. It does not decide market truth; local escalation games and Zoltar remain responsible for truth and branching.","title":"OpenOracle integration","topic":"Architecture","weight":1},{"fragment":"openoracle-role","heading":"Why Statoblast uses OpenOracle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Statoblast needs a REP/ETH price to enforce live backing and convert price-independent REP capacity ownership into ETH minting capacity. Price-gated operations include REP withdrawals, liquidations, and certain Escalation Game deposits. A stale or easily manipulated price could allow an unsafe withdrawal or make a healthy vault appear liquidatable. A price change updates aggregate ETH capacity arithmetically and never requires a vault scan. Statoblast therefore uses OpenOracle to obtain a fresh, economically contestable REP/ETH price. OpenOracle does not prove a price by consulting a trusted source. Instead, a reporter posts a WETH/REP position, and other participants can replace an inaccurate report through an, economically motivated correction trade. Statoblast configures WETH as token1 and REP as token2 . Keeping WETH on the exact side lets the coordinator size the report directly from ETH-denominated gas costs without first assuming a REP/ETH conversion price. When a report settles, the coordinator converts the settled token amounts into REP per ETH: lastPrice = amount2 ⋅ 10 18 amount1 The settled REP amount ( amount2 ) is scaled by the fixed price precision and divided by the settled WETH amount ( amount1 ). This raw-amount ratio is valid because the configured WETH and REP contracts both use 18 decimals. The coordinator does not normalize token decimals in the settlement callback. An accepted report becomes the coordinator's cached REP/ETH price. It is reusable only while it remains inside PRICE_VALID_FOR_SECONDS , currently five minutes. The coordinator does not meter how much operation volume uses that price during the freshness window.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"statoblast-integration","heading":"Integration architecture","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Every security pool has its own OpenOraclePriceCoordinator . The coordinator connects users, the pool, and the shared OpenOracle instance. It keeps the pool's cached REP/ETH price, pending report state, staged-operation queue, and settlement callback state. Keeping this state per pool prevents one pool's pending report or freshness window from becoming a protocol-wide queue. OpenOraclePriceCoordinator is deployed with an immutable OpenOracle address. For each origin or child security pool, SecurityPoolFactory asks PriceOracleManagerAndOperatorQueuerFactory for a fresh coordinator wired to the shared OpenOracle instance, shared WETH, and that universe's REP token. SecurityPoolDeployer passes the coordinator to the pool, after which the factory calls setSecurityPool once with the nonzero pool address. Only the configured pool may seed the coordinator's inherited lastPrice . A child pool inherits the parent's numeric price as a starting value, but its settlement timestamp remains zero, so that number is unusable until the child accepts its own report. The coordinator's validity window then determines whether the child price can be used. After OpenOracle settles, the coordinator validates the report. An accepted report refreshes the REP/ETH price and runs pending operations through local guardrails. A rejected report leaves the price cache unchanged and terminally fails the attached pending batch. Integration Flow The coordinator is the trust boundary between Statoblast operations and OpenOracle reports. After settlement, an accepted report refreshes the cache before guarded execution; a rejected report leaves the cache unchanged and terminally fails the attached batch.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Price request lifecycle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A liquidation or REP withdrawal begins at the pool's coordinator. REP deposits create capacity ownership directly from that deposit's selected target factor and do not use this staged oracle path. The coordinator first validates and stages a withdrawal or liquidation, then either uses a cached price or opens an OpenOracle report. Validate and stage the operation. The coordinator checks the caller, target vault, requested amount, and validity window before any report is requested. Check the cached price. If the cached REP/ETH price is fresh, the coordinator attempts the operation immediately. Open a report when the price is stale. The refresh sponsor supplies the request bounty and initial WETH/REP position, and the coordinator creates the report as its initial reporter. Allow corrections. Other participants may replace the report by posting the position required by OpenOracle. Every valid replacement installs a new reporter and restarts the settlement window. Settle the current report. When its settlement deadline is reached without another dispute, OpenOracle settles the report and calls the coordinator. Accept or reject the price. The coordinator validates the report identity, history, economics, amounts, and settlement-time base fee before updating the cache. Execute queued operations. An accepted fresh price allows the coordinator to attempt the queued pool operations, each of which still performs its own current safety checks. The complete flow is operation requested → cached price checked → immediate execution or report opened → report corrected or left unchanged → report settled → coordinator accepts or rejects → operation safety checks → execution .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Staging rules","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Liquidation and withdrawal amounts must be nonzero. validForSeconds must be positive and no greater than five minutes. A liquidation's receiver vault must differ from its target vault. The operator may be the target; operator identity is not used as a Sybil-resistant boundary. Non-liquidation operations must target the caller's own vault. Staging is disabled after the security pool's local Escalation Game has resolved. A liquidation whose receiver equals its target is rejected with Receiver is target before the coordinator requests or consumes an oracle report. A self-receiving operator uses a zero approval ID; a distinct receiver requires a bounded approval.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Execution batches","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"If a cached price is valid, the coordinator stages the operation record and then attempts immediate execution. Liquidation gates, a stale queue-time snapshot, a zero-effect withdrawal, or a downstream pool check may still cause the staged operation to fail and be consumed. When a new report is needed, one settlement callback can automatically attempt up to MAX_PENDING_SETTLEMENT_OPERATIONS = 4 operations. Additional staged operations remain active outside the callback batch. If they have not expired and the accepted price remains fresh, they can be executed later with executeStagedOperation .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Escalation Game deposits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Escalation Game REP deposits do not use the staged callback path. When a pool has active capacity ownership, depositToEscalationGame requires an already-fresh coordinator price and reverts when the price is stale. After previewing the accepted REP deposit, the pool uses lastPrice for the post-transfer vault and pool coverage checks.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"request-funding","heading":"Funding and report ownership","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Report funding is caller supplied. The coordinator's getRequestPriceCostAttoEth() getter calculates the request bounty from the current base fee, callback gas limit, and the coordinator's own report processing budget: requestPriceCostAttoEth = block.basefee ⋅ 4 ⋅ ( callbackGasLimit + gasConsumedOpenOracleReportPrice ) + 101 callbackGasLimit is the gas reserved for the settlement callback, gasConsumedOpenOracleReportPrice is the coordinator's own report-price callback work, and the small 101 offset keeps the forwarded bounty strictly above the computed gas product so the OpenOracle-funded reward path has a positive buffer instead of landing exactly on the boundary. If a cached price is usable, no new oracle request cost is retained and unused ETH is refunded. If a report is needed, only the first pending settlement slot retains the request cost. requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) forwards exactly the bounty to OpenOracle and refunds any excess. The same transaction submits the initial WETH/REP position, so the refresh sponsor funds both the ETH bounty and report position up front. Both public request paths refund only a positive unused or excess amount by making a low-level native-ETH call to the caller. A contract caller must accept that callback. If it rejects the refund, the whole transaction reverts, including operation staging, immediate execution, and any report opened in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"rolling-dispute-exclusivity","heading":"Sponsor lane","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator refuses a direct requestPrice call while the cached price is still fresh, preventing a redundant report from being opened over a usable price. Once the price is stale and a report is pending, only the original pendingReportSponsor may add operations to that in-flight settlement. Those follow-up operations pay no additional join fee; other callers must wait until the report finishes. Every successful dispute replaces the current report and restarts its settlement window. The resulting liveness consequences are described under Economic tradeoffs and limits .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Report sizing and correction incentives","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle's correctness depends on profitable arbitrage correcting inaccurate prices. The coordinator sizes each report so that correcting an error at or above the target remains profitable after modeled dispute costs and OpenOracle fees, with the configured security margin. In the conservative correction direction, the true REP/ETH price is (1 + targetError) times the reported price. Before gas, correcting that report releases the fraction (targetError - fees) / (1 + targetError) of the WETH position. The sizing formula therefore multiplies the modeled gas cost of one dispute by the security multiplier and divides by that correction-profit fraction. The coordinator sizes the initial WETH position to make correction economically profitable when the price deviates enough. Every dispute resets the oracle's reporting clock, so repeated disputes can delay settlement indefinitely. The position therefore also accounts for pool open interest. The minimum initial WETH position combines three inputs: Request-block base fee: a report sized from the current block.basefee . Lineage priority-fee assumption: a separate report sized from the immutable initialReportPriorityFeeAttoEthPerGas . Pool open interest: a floor equal to one percent of the pool's current settlement collateral. The priority-fee report is added to the larger of the base-fee report and the open-interest report. The sponsor may use this minimum or request a larger initial WETH position. reportAttoEth ( gasPriceAttoEthPerGas ) = ⌈ gasPriceAttoEthPerGas ⋅ gasUnitsForOneDispute ⋅ openOracleSecurityMultiplierBps ⋅ ( percentagePrecision + targetPriceErrorForDispute ) 10000 ⋅ ( targetPriceErrorForDispute - protocolFee - reporterFee ) ⌉ openInterestReportAttoEth = ⌈ settlementCollateralAttoEth 100 ⌉ minimumToken1ReportAttoEth = reportAttoEth ( initialReportPriorityFeeAttoEthPerGas ) + max ( reportAttoEth ( block.basefee ) , openInterestReportAttoEth ) escalationHaltAttoEth = max ( ⌊ initialWethReportAttoEth ⋅ escalationHaltMultiplierBps 10000 ⌋ , openInterestReportAttoEth ) The initial-report minimum adds a report derived from the lineage's immutable initialReportPriorityFeeAttoEthPerGas to the larger of a report derived from the request block's block.basefee and one percent of current pool open interest. The multiplicative-escalation halt is the greater of the selected initial report times the configured halt multiplier and one percent of the pool's stored ETH collateral backing complete sets. The open-interest division rounds up. No REP price, expected REP price movement, or individual protected-operation notional enters the minimum WETH calculation. The immutable priority fee is selected when the origin pool is created and inherited unchanged by its child pools. Open interest contributes ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ , with OPEN_INTEREST_DIVIDER = 100 , before the priority-derived report is added. The coordinator sets initialWethReportAttoEth = max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) , uses that value as OpenOracle's currentAmount1 , and derives amount2 = ⌈initialWethReportAttoEth * proposedRepPerEthPrice / 1e18⌉ in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Formula inputs","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Formula input Current value Source block.basefee Current request block EVM block context initialReportPriorityFeeAttoEthPerGas Origin-pool parameter; the UI defaults to 10 gwei Immutable lineage configuration inherited by child pools settlementCollateralAttoEth Current pool open interest in ETH units Configured security pool OPEN_INTEREST_DIVIDER 100 (a 1% floor) Coordinator constant gasUnitsForOneDispute 300,000 gas ORACLE_GAS_UNITS_FOR_ONE_DISPUTE openOracleSecurityMultiplierBps 100,000 bps ( 10x ) Factory constructor; initial default OPEN_ORACLE_SECURITY_MULTIPLIER_BPS targetPriceErrorForDispute 500,000 / 10,000,000 ( 5% ) Factory constructor; initial default ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE protocolFee 1% Coordinator/OpenOracle report parameters reporterFee 0.1% Coordinator/OpenOracle report parameters","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Construction bounds and parameter effects","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Coordinator construction requires: positive dispute gas units a target error no greater than 100% an Open Oracle Security multiplier of at least 1x combined OpenOracle fees below the target error It also rejects a priority fee whose constant-derived report would consume the reserved uint128 report or escalation-halt capacity; half of that capacity remains available for the dynamic base-fee or open-interest component. The factory stores its deployment configuration, so an invalid parameter domain causes coordinator deployment to revert. Moving the target error toward the fee floor increases the required WETH sharply. Increasing the Open Oracle Security multiplier increases the required WETH linearly. A larger target error reduces the required position, but permits a wider error before the modeled correction incentive applies. The immutable priority fee is an operator-selected transaction-inclusion assumption, not a tip observed from the request block. Setting it too low weakens the modeled incentive for a disputer when prevailing priority fees are higher. Setting it too high raises the sponsor's initial WETH and REP requirements and the initial-report-derived escalation halt, and can make requests impractical. Child pools inherit the origin's value. At a 30 gwei base fee and a configured 10 gwei priority fee, the modeled gas cost is 0.012 ETH . After applying the 10x Open Oracle Security multiplier and the worst-direction five-percent correction fraction after fees, the minimum report with 100 WETH of open interest is 3.230769230769230770 WETH . With the configured 10x halt multiplier, the initial-report-derived escalation halt is 32.307692307692307700 WETH . Dynamic WETH report estimator This calculator mirrors the onchain formula. The deployment parameters and caller-selected amount are shown as adjustable inputs to make their effect explicit. For a deployed coordinator, the base fee and pool open interest can change from one request block to another. Block base fee 30 gwei Initial-report priority fee 10 gwei Pool open interest 100 WETH Gas units for one dispute 300,000 gas Open Oracle Security multiplier 10.0x Target wrong-price error 5.0% OpenOracle protocol fee 1.0% OpenOracle reporter fee 0.1% Requested initial WETH 0.00 WETH Escalation halt multiplier 10x Initial-derived halt 32.307692307692307700 WETH Open-interest halt floor 1.000000000000000000 WETH Minimum token1 report 3.230769230769230770 WETH Selected initial WETH 3.230769230769230770 WETH Escalation halt 32.307692307692307700 WETH Modeled dispute gas cost 0.012000 ETH Buffered gas target 0.120000 ETH Correction profit fraction after fees 3.7143% Safety state fees below target error","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Settlement validation, rejection, and recovery","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Low-level callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle invokes the coordinator through a low-level callback whose success is intentionally neither stored nor emitted. A failed callback does not automatically undo an otherwise valid OpenOracle settlement. The coordinator accepts callbacks only from the configured OpenOracle and only for the current pending report id. If those checks revert inside the low-level call, the report can still settle while the coordinator remains pending. If the settlement transaction cannot retain enough gas headroom after the callback attempt, OpenOracle reverts settlement with InvalidGasLimit . In that case, the report has not settled and coordinator recovery is not yet available.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"settlement-validation-scope","heading":"Coordinator settlement validation","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A callback that successfully enters the coordinator can still produce a rejected price. The coordinator clears the pending report id, emits PriceReportRejected , and leaves the price cache unchanged when any of the following conditions holds: the settlement base fee exceeds the request-time cap; storedGame(reportId).numReports is saturated at the uint24 maximum, reported as Counter saturated ; the final history record's WETH amount is too small to support another dispute at that record's base fee plus the configured priority fee, reported as Report uneconomic ; either settled token amount is zero; or the integer REP/ETH calculation produces a zero price. When dispute tracking is enabled, OpenOracle records each report block's base fee. If the report counter is not saturated, the coordinator selects history index numReports - 1 and checks the final WETH position against the configured security-sizing formula using that record's base fee plus the configured priority fee. It also enforces the request-time settlement base-fee cap before recording the final ratio. These checks establish only that the final WETH position meets the modeled security floor and that the callback data is structurally acceptable. They do not prove that the accepted price is externally correct, that an independent corrector exists, or that a correcting transaction will obtain inclusion.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Rejected-price cleanup","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A rejected report does not replay its pending settlement operations. Instead, the coordinator terminally fails every operation attached to that report, removes it from the active and pending sets, and releases any liquidation-approval reservation exactly once. A caller must stage a new operation to try again with a later price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Recovering a settled report after callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"When OpenOracle has already settled but the coordinator remains pending because the low-level callback did not complete, anyone can call recoverSettledPendingReport . Recovery clears the pending report, sponsor, and base-fee cap; withdraws coordinator-owned OpenOracle balances; and terminally fails every pending settlement operation attached to the report. Recovery does not treat those operations as successful and does not accept the report as a new price. Each affected liquidation reservation is released during the same cleanup, so no approval quota remains locked.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Economic tradeoffs and limits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"No accepted-price notional budget","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator does not maintain a notional exposure budget. One accepted price may authorize multiple otherwise-valid operations during its freshness window. The report position is therefore an economic correction incentive, not a bond sized to the exact aggregate payoff of every operation that may use the price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Unbounded funded delay","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The stale-price refresh sponsor retains the staging lane while its report is pending. A timely valid dispute replaces the reporter and restarts OpenOracle's settlement clock. Repeated disputes can therefore delay settlement. Each extension requires a transaction and the contract-specified replacement position. Under ordinary non-dust parameters, fees and required position size also accumulate. There is intentionally no absolute coordinator deadline. The protocol does not claim liveness against a participant with unbounded capital that is willing to keep funding valid replacements. This availability model is separate from the dynamic initial WETH sizing rule and is not a bounded-liveness invariant.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Dispute escalation after the halt","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"In this integration, WETH is the escalation variable. Before escalationHalt , OpenOracle requires min(⌊oldAmount1 × multiplier / 100⌋, escalationHalt) . With the configured multiplier of 115 , integer flooring leaves token1 amounts from one through six attoETH unchanged; seven attoETH is the first amount that grows. At or above the halt, each replacement instead requires oldAmount1 + 1 , adding one attoETH of WETH. The selected initial amount is max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) . One escalation-halt candidate is that amount multiplied by the configured halt multiplier. The other is ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ . The actual halt is the greater of the two, as shown in Dynamic Report and Escalation Threshold .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"attack-model","heading":"External-payoff and censorship model","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"This stress test models an attacker that posts a manipulated REP/ETH report, excludes a correcting transaction until settlement, and receives an outside payoff if the accepted price makes a liquidation executable. It is a continuous approximation for reasoning about incentives, not a contract-enforced exposure cap or a guarantee that a correction will obtain inclusion. The payoff is binary: the attacker receives externalPayoff only when the manipulated price is strictly above the liquidation threshold and far enough beyond the configured liquidation-distance guard. The cost side grows with the report's WETH-denominated liquidity, the duration of censorship, and the price error above the modeled honest-dispute barrier. honestDisputeBarrierFraction is a model input that abstracts the cost an honest corrector must overcome before the model assigns censorship pressure. It is not a coordinator field or a separately configured OpenOracle parameter. The calculator's illustrative 1.1% default is only the fixed-fee floor: the configured 1% protocol fee plus 0.1% reporter fee. A deployment analysis must replace it with a full scenario-specific barrier. OpenOracle's report-cost derivation also depends on volatility over the settlement horizon and expected loss under adversarial disputes; gas, priority fees, price impact, and capital lockup can raise the barrier further. executionErrorThreshold = max ( 0 , liquidationThresholdPrice honestPrice ⋅ ( 1 - minLiquidationPriceDistanceBps 10000 ) - 1 ) manipulatedPriceError = max ( 0 , manipulatedPrice - honestPrice honestPrice ) liquidationExecutable = manipulatedPrice > liquidationThresholdPrice ∧ manipulatedPriceError ≥ executionErrorThreshold attackerPayoff = liquidationExecutable ? externalPayoff : 0 censorshipRate = max ( 0 , manipulatedPriceError - honestDisputeBarrierFraction ) censorshipCost = censorshipDuration ⋅ censorshipRate ⋅ oracleReportLiquidity Price errors and the dispute barrier are fractions of the honest price. externalPayoff and oracleReportLiquidity use the same ETH-denominated unit. The liquidation design owns the exact integer threshold and distance checks. The derived duration threshold applies only when the manipulated price unlocks the outside payoff. It asks how many abstract model steps are required for censorship cost to reach a chosen multiple of that payoff. targetGriefRatio is the desired attacker loss after subtracting externalPayoff , divided by that payoff. A value of 1 therefore targets censorship cost equal to twice the payoff, leaving a modeled net loss equal to the payoff. One censorship step is the interval over which the chosen censorshipRate and report-liquidity cost accrue; it is not inherently a block, second, or dispute round. A deployment analysis must define that time mapping before applying the threshold. When a payoff is unlocked, the result is unbounded if the modeled censorship rate is zero; otherwise it shrinks as price-error pressure or report liquidity relative to the outside payoff increases. oracleLiquidityRatio = oracleReportLiquidity externalPayoff safeCensorshipDuration = { targetGriefRatio + 1 censorshipRate ⋅ oracleLiquidityRatio , liquidationExecutable ∧ censorshipRate > 0 ∞ , liquidationExecutable ∧ censorshipRate = 0 not applicable , otherwise safeCensorshipDuration is the model's historical name for this cost threshold; it does not establish a maximum protocol delay. Repeated funded disputes can restart the settlement window as described under Economic tradeoffs and limits . Binary censorship stress test Adjust the liquidation boundary, outside payoff, report liquidity, and censorship assumptions. The barrier starts at the fixed-fee floor and must be calibrated for the modeled deployment. Presets provide executable, guarded, and long-censorship scenarios. Honest price 100 Manipulated price 113 Liquidation threshold 101 Liquidation distance 1000 bps External payoff 1000.00 ETH Report liquidity 4000.00 ETH Honest dispute barrier (model) 1.10% Censorship duration (model steps) 24 steps Target excess-cost ratio 1.0 Execution error threshold 12.22% Manipulated price error 13.00% Liquidation executable yes Attacker payoff 1000.00 ETH Censorship cost 11424.00 ETH Oracle liquidity ratio 4.00 Duration to target cost (model steps) 4.20","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"parameters","heading":"OpenOracle parameters used","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Use the coordinator reference for source constants and parameter definitions, and deployment verification for deployed getter values. The Statoblast whitepaper links to both. Parameter Current integration value Use in Statoblast token1Address / token2Address WETH / REP Defines the REP/ETH price pair used for security-pool solvency checks. minimumToken1ReportAttoEth() Dynamic; see Dynamic Minimum WETH Report and the estimator . Adds the priority-fee report to the larger base-fee or open-interest correction floor. initialReportPriorityFeeAttoEthPerGas Configured per origin pool; UI default 10 gwei Provides an immutable inclusion-fee component that every child pool inherits. OPEN_INTEREST_DIVIDER 100 Makes one percent of settlementCollateralAttoEth the open-interest-derived initial-report and escalationHalt floor. OpenOracle initial currentAmount1 The greater of minimumToken1ReportAttoEth() and the sponsor's requestedInitialAttoWeth The sponsor may request and fund more than the minimum; the coordinator submits the selected amount as currentAmount1 . escalationHalt The greater of 10x the selected initial currentAmount1 and 1% of pool open interest Raises the multiplicative-escalation threshold with pool exposure and stops multiplicative report-size escalation there; later disputes add one attoETH of WETH at a time. settlerRewardAttoEth Dynamic; see Oracle Request Cost . The full request bounty is assigned to the account that settles the report and triggers the callback. settlementTime 480 seconds (8 minutes) Sets the timestamp-based report settlement delay. The 40 * 12 derivation assumes twelve-second blocks, but the configured OpenOracle timeType uses seconds. Staged operations expire after this delay plus their operation-specific validity window. disputeDelay 0 Allows disputes immediately after each report and strictly before its settlement deadline. At the deadline, settlement is valid and a dispute is too late. callbackGasLimit Derived from settlement gas and the maximum callback batch; see Callback Gas Limit . Sets the callback gas limit for up to MAX_PENDING_SETTLEMENT_OPERATIONS staged operations; settlement still needs enough surrounding gas to satisfy OpenOracle's callback headroom check. feePercentage / protocolFee 10000 / 100000 Configures OpenOracle reporter and protocol fee accounting. Those fees contribute to the correction friction abstracted by honestDisputeBarrierFraction in the external-payoff model . protocolFeeRecipient 0x000000000000000000000000000000000000dEaD Receives OpenOracle protocol fees for the coordinator-created report instance. multiplier 115 Before escalationHalt , requires WETH token1 equal to ⌊prior amount × 1.15⌋ , capped at the halt. Integer flooring leaves prior amounts of one through six attoETH unchanged. At or above the halt, each dispute requires one more attoETH of WETH. timeType / trackDisputes true / true Uses timestamps. Coordinator reports always enable dispute history so settlement can validate the final report's recorded base fee. Settlement rejects the price if the uint24 report counter is saturated. callbackContract OpenOraclePriceCoordinator Routes settled amounts back into Statoblast's price cache and staged-operation executor.","title":"OpenOracle integration","topic":"Architecture","weight":0}] +window.statoblastDocsSearch = [{"fragment":"","heading":"","keywords":["ABI","transactions","callers","events"],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"For developers and operators: find the contract that handles each state change, who can call it, and what the call does. Quick index ZoltarQuestionData Zoltar ReputationToken SecurityPoolFactory SecurityPool SecurityPoolForker EscalationGame LiquidationApprovalRegistry OpenOraclePriceCoordinator ShareToken UniformPriceDualCapBatchAuction","title":"Contract interactions","topic":"Contracts","weight":1},{"fragment":"zoltarquestiondata","heading":"ZoltarQuestionData","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates immutable, content-addressed scalar or categorical questions and exposes their display metadata. Source Read surface: Use getQuestionId before submission; questionCreatedTimestamp and questions for direct lookup; getQuestionCount and getQuestions for indexed or paged discovery; and getQuestionEndDate , getOutcomeLabels , splitUint256IntoTwoWithInvalid , hasNonZeroScalarReservedBits , isMalformedAnswerOption , and getAnswerOptionName when validating or displaying answers. In the QuestionData tuple, startTime and endTime are uint48 , while numTicks is uint120 ; clients must use these exact widths because they determine the getQuestionId and createQuestion selectors. Transaction Caller Main prerequisites State or asset effect Primary signals createQuestion(questionData, outcomeOptions) Anyone Question ID not already created; end time is on or after start time. Scalar questions use no labels, require display maximum greater than minimum, and positive ticks. Categorical questions require nonempty labels whose keccak256(abi.encode(label)) values are strictly descending. Stores the question at its deterministic content hash, records the creation timestamp, appends it to discovery order, and stores categorical labels when supplied. QuestionCreated","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"zoltar","heading":"Zoltar","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Registers universe forks, charges the fork admission haircut, and mints branch-specific child REP. Source Read surface: Use universes , forkThresholdDivisor , forkBurnDivisor , zoltarQuestionData , genesisReputationToken , getForkTime , forkQuestionMatches , getRepToken , getForkThresholdAttoRep , getNonDecisionThresholdAttoRep , getUniverseTheoreticalSupplyAttoRep , getChildUniverseId , getDeployedChildUniverses , and getMigrationRepBalanceAttoRep to reconstruct universe and migration state. Construction requires a deployed genesis REP token with theoretical supply from one attoREP through 11 million REP and forkBurnDivisor >= 5 , which caps the uncredited fork haircut at 20% of the threshold. Security boundaries for these calls are A15 intended question selection and A25 safe immutable parameters . Transaction Caller Main prerequisites State or asset effect Primary signals forkUniverse(universeId, questionId) Any address able to fund the current fork threshold Initialized and unforked universe; existing ended question; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Records the fork, removes threshold REP from the parent universe, and credits the caller with the threshold minus the configured uncredited haircut. UniverseForked burnRep(universeId, amountAttoRep) Any REP holder; the caller can burn only its own balance Initialized universe; positive amount; sufficient caller REP and theoretical supply. Genesis REP requires allowance. Permanently removes REP without creating migration credit; escalation settlement uses this when the haircut was not paid through its own fork. RepBurned and the token burn or transfer event deployChild(universeId, outcomeIndex) Anyone Parent forked; outcome is well formed; child is not already deployed. Deploys the deterministic child REP token and initializes the child universe. DeployChild addRepToMigrationBalance(universeId, amountAttoRep) Parent REP holder Universe forked; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Burns or sinks additional parent REP and increases the caller's reusable migration balance. MigrationRepAdded splitMigrationRep(universeId, amountAttoRep, outcomeIndexes) Migration-balance holder Universe forked. A nonempty list additionally requires every outcome to be well formed and the cumulative amount per child not to exceed the caller's migration balance. Mints amount of child REP into every selected branch, deploying missing children lazily. An empty outcome list returns after the universe-fork guard without outcome validation, deployment, minting, or events. A nonempty zero-amount call still validates every outcome, may deploy missing children, performs zero-value child REP mints, and records a zero split for every branch. TheoreticalSupplySet and DeployChild when needed; child REP Transfer and Mint , then MigrationRepSplit , per selected branch, including at zero amount; no event for an empty list","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"reputationtoken","heading":"ReputationToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Implements universe-specific ERC-20 REP and enforces the supply ceiling maintained by Zoltar. Source Read surface: Use getTotalTheoreticalSupplyAttoRep , zoltar , and the standard ERC-20 name , symbol , decimals , totalSupply , balanceOf , and allowance reads. Transaction Caller Main prerequisites State or asset effect Primary signals setMaxTheoreticalSupplyAttoRep(totalTheoreticalSupplyAttoRep) Zoltar only Called by Zoltar as part of child-universe creation; theoretical supply does not exceed 11 million REP. Sets the child token theoretical-supply ceiling used to bound subsequent migration mints. TheoreticalSupplySet mint(account, valueAttoRep) Zoltar only account is nonzero; resulting ERC-20 supply does not exceed theoretical supply. Mints branch REP to an account. Mint and ERC-20 Transfer burn(account, valueAttoRep) Zoltar only account is nonzero and has sufficient REP; theoretical supply covers the burn. Burns account REP and reduces both actual and theoretical supply by the same amount. Burn and ERC-20 Transfer transfer(to, value) REP holder Destination is nonzero; caller has sufficient balance. Moves REP from the caller without changing actual or theoretical supply. Transfer approve(spender, value) Any REP account setting its own allowance Spender is nonzero. Replaces the named spender allowance without moving REP. Approval transferFrom(from, to, value) A spender with sufficient allowance from from Source and destination are nonzero; source has sufficient balance; caller has sufficient allowance, including when caller equals source. Moves REP from from ; a finite allowance decreases by value , while an infinite allowance remains unchanged. Neither allowance path emits Approval . Transfer only","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolfactory","heading":"SecurityPoolFactory","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates and canonically registers origin and child security pools with their share token, oracle coordinator, and optional truth auction. Source Read surface: Use initialEscalationGameDepositAttoRep , minimumSecurityBondDebtAttoEth , and minimumVaultRepDepositAttoRep for immutable deployment floors. The factory requires the escalation baseline to equal 1 REP, so each pool fixes its effective escalation deposit at construction as exactly max(1 REP, theoretical REP supply / 10,000,000) . A zero configured vault REP floor selects the default theoretical REP supply / 100,000 ; a nonzero constructor value is the exact override. The security-bond debt floor defaults to 1 ETH. Use securityPoolDeploymentCount with the strict securityPoolDeploymentsRange(startIndex, count) pager, which reverts rather than truncating when the requested range exceeds the array. Use getOriginId , getPoolId , getSecurityPool , getSecurityPoolOriginId , and getSecurityPoolHasInheritedForkOutcome for canonical lookup. Transaction Caller Main prerequisites State or asset effect Primary signals deployOriginSecurityPool(universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas) Anyone statoblastSecurityMultiplierBps > 10_001 , which makes the halfway migration component strictly greater than one; the effective pool-held vault REP backing multiplier separately floors that component at the 10,500-BPS liquidation-award reserve described by the liquidation design . initialReportPriorityFeeAttoEthPerGas > 0 and remains within the coordinator-computed OpenOracle uint128 report/escalation-halt capacity bound; question exists and has exactly the categorical labels Yes , then No ; universe is unforked and has a REP token; the non-decision threshold exceeds the construction-time effective escalation deposit max(1 REP, theoretical REP supply / 10,000,000) ; the origin/universe/priority-fee slot has not already been claimed. Creates the canonical origin pool, its lineage-wide share token, and its price coordinator with the configured initial-report priority fee, then wires and registers them atomically. SecurityPoolRegistered , then DeploySecurityPool deployChildSecurityPool(parent, shareToken, universeId, questionId, statoblastSecurityMultiplierBps, currentRetentionRate, settlementCollateralAttoEth) SecurityPoolForker only Parent is the canonical pool for its lineage; supplied share token equals the parent share token; target origin/universe slot is unclaimed; deployment arguments satisfy downstream constructors and wiring. Creates and registers a canonical child pool with a coordinator that inherits initialReportPriorityFeeAttoEthPerGas from the parent coordinator and a forker-owned truth auction, while retaining the parent lineage share token. SecurityPoolRegistered , then DeploySecurityPool","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypool","heading":"SecurityPool","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Holds ETH collateral and REP underwriting, accounts for vaults and fees, mints shares, and routes local escalation. Source Read surface: Immutable relationship and configuration getters are questionId , universeId , initialEscalationGameDepositAttoRep , zoltar , parent , shareToken , repToken , priceOracleManagerAndOperatorQueuer , openOracle , escalationGameFactory , questionData , securityPoolForker , truthAuction , securityPoolFactory , and statoblastSecurityMultiplierBps ; the current game is escalationGame . Accounting getters include totalCapacityOwnershipAttoRep , settlementCollateralAttoEth , totalRepBackingUnits , shareTokenSupplyAttoShares , securityVaults , minimumSecurityBondDebtAttoEth , minimumVaultRepDepositAttoRep , totalBadDebtAttoEth , and vaultBadDebtAttoEth . lastDepositTargetHealthFactorBpsByVault is deposit-instruction metadata, not a vault health measurement. Use getVaultCapacityBackingFactorsBps for current associated and pool-held REP-per-capacity ratios, getCurrentMintingCapacityAttoEth for price-converted aggregate capacity, and getVaultOpenInterestAttoEth for a vault’s live proportional obligation. Other derived and paged reads are getVaultCount , getVaults , attoSharesToAttoEth , attoEthToAttoShares , attoRepToBackingUnits , backingUnitsToAttoRep , getTotalPoolHeldAttoRep , totalAccruedFeesAttoEth , getPoolAccountingSnapshot , getVaultFeeRemainder , and isEscalationResolved . The backing-factor ratios are not current vault health: associated REP includes dispute-staked principal as at-risk security, while current health also depends on OI, REP/ETH price, the security multiplier, and both protocol constraints. The vault registry is append-only and newest-registered first. Registration requires only a nonzero address and can occur without economic state; consumers filter current positions from securityVaults , escalation stake, and bad debt. isEscalationResolved() is true only when a local escalation game is configured and the forker routes a non- None outcome; an operational fixed-outcome child without a local game returns false. Lifecycle and fee getters are totalClaimableVaultFeesAttoEth , lastUpdatedFeeAccumulator , feeIndex , currentRetentionRate , awaitingForkContinuation , and systemState . Price-sensitive withdrawal, dynamic-capacity, and liquidation calls depend on A16 timely inclusion , A21 genesis REP and WETH behavior , A19 observable correctable price , and A06 lifecycle executors . User-initiated pool calls additionally depend on A28 account authority . Transaction Caller Main prerequisites State or asset effect Primary signals burnEscalationWinnerHaircut(amountAttoRep) This pool's EscalationGame only Caller is the configured escalation game; amount is positive and the game has already transferred enough REP to the pool. Burns the winning-deposit haircut from REP already escrowed in the game. RepBurned and ERC-20 Transfer ; child REP also emits Burn depositRepToVault(attoRepAmount, targetHealthFactorBps) Vault owner Operational and unforked; isEscalationResolved() is false; deposit amount is positive; deposit target factor is at least 10,000; resulting vault REP meets the configured supply-scaled minimum. Transfers REP into the pool, credits proportional REP backing units, and creates REP-denominated fee-earning capacity ownership from this deposit and its selected deposit target factor. RepDepositedToVault , VaultDepositTargetHealthFactorRecorded , and accounting checkpoints redeemFees(vault) Anyone; any nonzero ETH payment is always sent to vault A nonzero payment path requires vault to accept ETH. First accrues the vault's fees. If resulting claimable fees are zero, returns without payment; otherwise clears and pays the full amount. Accrual checkpoints only when accrual state changes; both VaultAccountingCheckpoint and PoolAccountingCheckpoint for a nonzero redemption; no event when fees and accrual state are unchanged createCompleteSet() with ETH Trader Operational and unforked; isEscalationResolved() is false; not awaiting continuation; positive ETH converts to at least one complete-set unit; live oracle-priced minting capacity covers the resulting settlement collateral, not merely this deposit; any explicit unassigned auction position remains healthy after the mint; under A22 asset-recipient compatibility , a contract trader accepts onERC1155BatchReceived . Adds collateral and mints one Invalid , Yes , and No share per complete-set unit, then invokes the ERC-1155 batch-receiver callback for a contract trader. Callback rejection rolls back the ETH, pool accounting, events, and share mint. CompleteSetCreated , PoolAccountingCheckpoint , then ERC-1155 TransferBatch on a successful callback redeemCompleteSet(amountAttoShares) Anyone; positive redemption requires the caller to hold the complete set Operational and unforked; caller holds every outcome amount requested; caller accepts the resulting ETH call, including zero value. Zero is accepted without a token balance. Burns equal balances of all three outcomes and pays amountAttoShares * settlementCollateralAttoEth / shareTokenSupplyAttoShares using the pool's remaining economic claim supply as its collateral denominator. Complete-set issuance adds to that denominator, while complete-set and winning-share redemption consume it; fork-time source entitlements materialize without changing it because their claims are already reserved. Zero passes the token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. CompleteSetRedeemed and PoolAccountingCheckpoint redeemShares() Anyone; a positive payout requires the caller to hold winning shares Operational pool with a final outcome; caller accepts the resulting ETH call, including zero value. Burns the caller's full winning balance and pays its pro-rata remaining collateral. A zero winning balance passes token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. SharesRedeemed and PoolAccountingCheckpoint redeemRepFromVault(vault) Anyone; REP is always sent to vault Operational pool with a final outcome; the specified vault has no escalation escrow and has redeemable REP. Burns the vault's REP backing units and returns its proportional vault REP backing. RepRedeemedFromVault depositToEscalationGame(outcome, maxAmount) Vault owner Question end has passed; pool operational in an unforked universe, without an inherited fixed outcome, and not awaiting continuation. On the first deposit, the live non-decision threshold must exceed one attoREP; outcome and amount accepted; the remaining vault and aggregate pool totals each preserve both live open-interest health branches; a fresh price is required when total capacity ownership is nonzero. Deploys the local game on the first deposit. The game factory uses the configured start bond while it is below the live non-decision threshold; if tracked REP supply later makes it too large, the factory uses nonDecisionThresholdAttoRep - 1 instead. Repeat deposits use the existing game's stored startBondAttoRep and nonDecisionThresholdAttoRep . Every accepted deposit removes enough REP backing units and escrows dispute-staked REP on the selected outcome. EscalationGameSet on first deposit; DepositToEscalationGame withdrawFromEscalationGame(outcome, depositIndexes) Anyone; a nonempty list must select deposits belonging to one original depositor Game configured; operational pool; valid final outcome. If an external fork interrupted the game, parent withdrawal stays unavailable: winners settle in the child by carried proof, inherited losers require no transaction, and unresolved parent escalation-deposit accounting cleanup is optional. A nonempty list additionally requires valid local indexes and one common depositor. A nonempty list settles local deposits and pays winning REP to the immutable depositor recorded by each deposit. Liquidation cannot change that payout address. An empty list returns after the outer lifecycle checks without settlement, state change, or event. Per processed deposit, escalation-game CarryDepositConsumed ; additionally ClaimDeposit for a winning payout. No event for an empty list withdrawForkedEscalationDeposits(outcome, proofs) Anyone; a nonempty list must name one original depositor across all proofs Game configured; operational child pool; valid final outcome. A nonempty list additionally requires an initialized and fully resumed continuation game, valid unconsumed winning proofs, and one common depositor. A nonempty list verifies and consumes carried proofs, then pays winning child REP to the immutable depositor committed in each leaf. Stable continuation identities retain the creating game, and the cumulative retention-index ratio applies every intervening auction haircut in constant ancestry work. An empty list returns after the outer lifecycle checks without proof verification, state change, or event. Per processed proof, escalation-game CarryDepositConsumed and ClaimDeposit . No event for an empty list updateSettlementCollateral() Anyone No caller or lifecycle restriction. It returns unchanged when the accumulator is already at or beyond the clamped timestamp. Accrues elapsed fees through question end while this pool's universe remains unforked; after that universe forks, its fork timestamp replaces question end as this pool epoch's cutoff, including a later question-end-to-fork interval. The cutoff is local to this pool: an activated child starts a separate fee epoch. It moves whole credited fees from settlement collateral into the unallocated accrued-fee reserve and advances the accumulator. With positive elapsed time but zero fee-eligible capacity ownership it clears denominator-specific remainder and advances the timestamp without charging fees. PoolAccountingCheckpoint whenever positive elapsed time is processed, including the zero-capacity-ownership branch; no event for an unchanged timestamp updateRetentionRate() Anyone No caller restriction. It returns unchanged when the pool is not Operational or the calculated rate equals the stored rate. Zero live minting capacity selects the maximum retention rate. Recalculates the retention rate from current collateral and live oracle-priced minting capacity. PoolAccountingCheckpoint only when the stored retention rate changes; no event for a no-op updateVaultFees(vault) Anyone for any address No caller, nonzero-vault, or lifecycle restriction. First updates pool accrual, then advances the vault fee index and fractional remainder, moves whole assigned fees from reserve to the vault, registers any previously unseen nonzero vault address regardless of economic state, and returns leftover reserve to settlement collateral once a forked pool has checkpointed all fee-eligible capacity ownership. Accrual PoolAccountingCheckpoint when due; VaultAccountingCheckpoint when the vault index, remainder, or claimable fee balance changes; an additional PoolAccountingCheckpoint when pool accounting changes; no event when neither accrual nor vault or pool accounting changes withdrawRepFromVault(vault, attoRepAmount) This pool's OpenOraclePriceCoordinator only Fresh coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; no vault REP escrow; the remaining vault and aggregate pool totals each meet the upward-rounded associated-REP and free-REP backing requirements, with equality healthy. Removes the requested proportional REP backing units, or all backing units when the requested remainder would fall below the REP minimum; proportionally reduces the vault and pool capacity ownership; recalculates retention; and transfers the resulting withdrawable REP to vault . REP Transfer ; RepWithdrawnFromVault ; VaultAccountingCheckpoint ; and applicable fee-accrual or retention PoolAccountingCheckpoint events performLiquidation(request) This pool's OpenOraclePriceCoordinator only In ABI order, request contains operationId , operator , receiverVault , targetVault , requestedDebtAttoEth , snapshot , minimumReceiverHealthFactorBps , and minLiquidationPriceDistanceBps . The nested snapshot contains targetBackingUnits , targetCapacityOwnershipAttoRep , totalPoolHeldAttoRep , and totalRepBackingUnits . Fresh settled coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; receiver differs from target. The target backing and capacity-ownership snapshot fields must match; the two pool-total snapshot fields are reconstruction evidence, while execution uses live pool totals. After target and receiver fee checkpoints, the liquidation delegate requires live target backing, dispute-staked REP, and open interest to remain at least minLiquidationPriceDistanceBps beyond the liquidation threshold and requires the live target state to remain unhealthy. When debt moves, the receiver must satisfy the protocol backing checks multiplied by its approved minimum health factor, using live post-liquidation state and upward-rounded requirements; its resulting debt must meet the configured debt floor and its REP must meet the vault floor. The target resulting debt must be zero or meet the debt floor; when debt remains, target REP must meet the vault floor. Capped by the target vault's open interest and fundable REP award, a nominal debt quote selects proportional capacity ownership rounded downward and moves that ownership to the explicitly selected receiver vault. Moved security-bond debt is the receiver's exact live open-interest increase and cannot exceed the nominal quote or request. On a delegated route, the coordinator additionally bounds it by the staged approval reservation; the self-receiving route has no approval reservation. The operator only submits the transaction. Dispute-staked REP claims, accrued claimable fees, surplus vault REP backing, and unmatched ownership remain with the target. On a full-target request, target open interest minus exact moved debt is recorded as attoETH-denominated bad debt; that residual can include both an award-unfunded slice and integer-allocation residue. Receiver or target dust cannot turn otherwise funded debt into bad debt. Fee-accrual and target or receiver VaultAccountingCheckpoint events as needed; VaultLiquidated identifies operation, operator, receiver, target, moved debt, moved ownership, and bad debt; VaultBadDebtRecorded records residual target debt on a full-target request; final pool accounting checkpoint setStartingParams(...) SecurityPoolFactory only Factory caller. The pool has no internal one-shot or lifecycle guard; the factory exposes it only through atomic deployment wiring. Sets the fee timestamp, retention, and collateral, seeds the coordinator with zero for an origin or the parent's last price for a child, then checkpoints initialization. Coordinator RepEthPriceSet and CoordinatorStateCheckpoint , then pool PoolAccountingCheckpoint , even for zero or repeated values if the factory were to call again activateForkMode() SecurityPoolForker only The pool has no inherited fixed outcome, so a fixed child cannot reopen for a later universe fork. There is no current-state guard otherwise. A configured game's drain must succeed or the entire activation reverts without propagating its reason data. Sets PoolForked , accrues through the fork clamp, transfers the pool's entire REP balance to the forker, then makes the pool drain its configured escalation game's entire REP balance to the forker. Repeated calls are not lifecycle-guarded and transfer any balances replenished since the prior call before repeating the checkpoints. Pool-held REP Transfer always, including at zero; configured-game REP Transfer only for a positive game balance; accrual checkpoint when due; always PoolForkModeActivated and fork-activation PoolAccountingCheckpoint initializeForkedEscalationGame(...) SecurityPoolForker only No game is configured; downstream startFromFork parameters are valid. Deploys and starts the pool's paused fork-continuation game with inherited timing and optional fixed outcome. Escalation GameContinuedFromFork , then pool EscalationGameSet initializeForkCarrySnapshotWithResolutionBalances(...) SecurityPoolForker only A game is configured; it is a fork continuation with no prior snapshot; leaf counts fit the MMR; supplied or computed snapshot ID matches the data. Installs the continuation game's immutable carry peaks, counts, totals, resolution balances, and normalized nullifier roots. ForkCarryCheckpoint resumeForkedEscalationGame() Anyone Pool is operational, awaiting a configured fork continuation, and the game has not resumed. Checks the already-installed immutable carry commitment and aggregate REP funding, clears the pool wait flag, records the resume timestamp, and starts the continuation's remaining escalation clock in one bounded call. ForkContinuationResumed and AwaitingForkContinuationSet(false) setAwaitingForkContinuation(shouldAwait) SecurityPoolForker only No lifecycle or value-change guard. Stores whether complete-set minting must wait for continuation initialization. AwaitingForkContinuationSet , including for a repeated value setSystemState(newState) SecurityPoolForker only No transition or value-change guard. Replaces the pool lifecycle state directly. SystemStateSet , including for a repeated state configureVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, lastDepositTargetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth) SecurityPoolForker only vault is nonzero; no lifecycle or value-change guard. Replaces the vault REP backing units, price-independent capacity ownership, fee index, latest-deposit preference metadata, vault bad debt, and aggregate pool bad debt, clears pooled fee-index remainder when capacity ownership changes, and registers the nonzero vault address regardless of the supplied state. Always VaultAccountingCheckpoint and PoolAccountingCheckpoint , including when all supplied values repeat current state configureFinalizedAuctionVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, lastDepositTargetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth) SecurityPoolForker only vault is nonzero; the forker has already included the sold capacity ownership in the pool fee denominator at auction finalization. Assigns finalized-auction REP backing and already-fee-eligible capacity ownership to the winning vault without clearing the pool-wide fee-index remainder, preserves its latest-deposit preference metadata without inventing one for a new vault, then replaces the vault fee index, bad debt, and aggregate pool bad debt and registers the vault. Always VaultAccountingCheckpoint and PoolAccountingCheckpoint , including when all supplied values repeat current state setTotalRepBackingUnits(newDenominator) SecurityPoolForker only No lifecycle or value-change guard. Replaces the REP backing units denominator. TotalRepBackingUnitsSet , including for zero or a repeated value setTotalSharesAttoShares(newTotalSharesAttoShares) SecurityPoolForker only No lifecycle or value-change guard. Replaces stored shareTokenSupplyAttoShares , the denominator used by attoSharesToAttoEth and complete-set redemption. ShareTokenSupplySet , including for zero or a repeated value setPoolFinancials(newSettlementCollateralAttoEth, newTotalCapacityOwnershipAttoRep, newFeeEligibleCapacityOwnershipAttoRep, newTotalBadDebtAttoEth) SecurityPoolForker only Fee-eligible capacity ownership does not exceed total capacity ownership, and the supplied settlement collateral does not exceed the current price-converted minting capacity; no lifecycle or value-change guard. Replaces settlement collateral, both price-independent capacity-ownership totals, and aggregate pool bad debt, resets the fee timestamp to the current block, and clears fee-index rounding carry. PoolAccountingCheckpoint , including for repeated financial values authorizeChildPool(pool) SecurityPoolForker only This parent pool is already authorized; candidate reports this share token; candidate universe has no different canonical pool. No pool-lifecycle guard. Asks the lineage share token to establish pool as the canonical authorized pool for its universe; reauthorizing the same pool is a no-op. AuthorizationUpdated only on first authorization; no event when already authorized transferEth(receiver, amountAttoEth) SecurityPoolForker only Fee liabilities are covered; amount fits both unreserved pool ETH and tracked settlement collateral; receiver accepts the ETH call, including zero value. Reduces tracked settlement collateral by amount , checkpoints the reconciliation, and calls receiver with that ETH. At zero amount it reduces no settlement collateral but still emits the checkpoint and performs a zero-value call; callback rejection rolls back the transaction and checkpoint. PoolAccountingCheckpoint , including at zero amount; no dedicated ETH-transfer event assignFinalizedAuctionFees(vault, amountAttoRep, auctionFeeIndexAtFinalization) SecurityPoolForker only The supplied finalization index does not exceed the current fee index; the forker credit workflow has already checkpointed the vault and added the claimed capacity ownership. Assigns to the claiming vault the fees its auctioned capacity ownership accrued from truth-auction finalization through the current fee index, combines fractional carry with the vault remainder, and removes whole assigned fees from the unallocated reserve. The ownership was already included in the fee denominator and total ownership at finalization. This call preserves aggregate fees and settlement collateral; after every eligible ownership is reconciled on a forked pool, a subsequent permissionless updateVaultFees checkpoint returns any reserve that no vault can individually claim to settlement collateral. VaultAccountingCheckpoint and auction-claim PoolAccountingCheckpoint ; the calling forker emits ClaimAuctionProceeds only after the broader credit workflow completes Direct ETH transfer to receive() Forker, this pool's truth auction, or parent pool only Sender is one of the three authorized protocol addresses. Forced ETH bypasses this ordinary-call guard. Accepts protocol-routed ETH used by migration and auction settlement. Forced ETH remains raw, unaccounted surplus rather than settlement collateral or fees. No dedicated receive event; the calling protocol step emits its own event","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolforker","heading":"SecurityPoolForker","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Freezes parent pools, creates selected child pools, migrates vault and escalation state, and settles collateral-repair auctions. Source Read surface: Use zoltar , forkData , getMigratedAttoRep , getForkActivationTime , getUnassignedPosition , getUnassignedPositionFeeIndex , isEscalationDepositClaimedDirectly , getEscalationDepositId , getDirectlyClaimedEscalationPrincipal , isEscalationWinnerHaircutPaidByFork , getEscalationMigrationEntitlementStatus , getOwnForkRepBuckets , getOwnForkMigrationStatus , getMigrationProxyAddress , getQuestionOutcome , attoRepToBackingUnits , and backingUnitsToAttoRep to reconstruct fork progress and preview migration conversions.","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"child-game-trust-boundary","heading":"Child-game trust boundary","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Fork entrypoints and child setup may receive contracts through unauthenticated pool lineages. External-universe initiation requires the supplied pool to be authorized by its declared share token, but that relationship alone does not prove factory registration; own-game initiation does not perform that authorization check. Canonicality comes from the configured SecurityPoolFactory registry. A game relationship check is point-in-time: the reported nonzero game address must return the supplied pool or child from securityPool() when validated. This does not prove that an arbitrary game getter is immutable or that the address was factory-deployed. Child setup captures one reported game address, validates it before privileged use, and reuses that exact address for continuation backing and escrow work. When unresolved escalation requires a continuation and setup initially reports no game, initialization creates one; the forker then captures and validates it before continuation use. Combined vault migration passes the captured child/game pair into unresolved cleanup without reading the child getter again. Truth-auction completion performs a fresh point-in-time validation of the game reported then before checking continuation readiness. Genuine factory-deployed EscalationGame instances store their pool immutably, but safety on unauthenticated paths does not assume arbitrary contracts do. Transaction Caller Main prerequisites State or asset effect Primary signals initiateSecurityPoolFork(securityPool) Anyone Pool operational with no inherited fixed outcome; the pool is authorized by its declared share token; its universe already forked; fork state not initialized; if an escalation game exists, it reports the supplied pool from securityPool() when validated and the universe fork occurred before that game settled. Declared-token authorization is not configured-factory registration; see the child-game trust boundary . Freezes the supplied pool after an external universe fork, drains its pool and game REP, and records a migration snapshot keyed by that address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot and ParentRepLocked ; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists forkZoltarWithOwnEscalationGame(securityPool) Anyone Pool operational with no inherited fixed outcome; its escalation game reports the supplied pool from securityPool() when validated and canTriggerOwnFork() is true because it recorded a local non-decision or inherited a threshold tie without a game-level fixed outcome; universe not already forked. The game-local predicate does not bypass the pool guard. Unlike external-universe initiation, this entrypoint does not require declared-share-token authorization; neither path authenticates the supplied address against the configured pool factory. See the child-game trust boundary . Uses the supplied pool game's non-decision to fork Zoltar, freezes that pool, and records own-fork REP buckets and snapshot state keyed by its address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot , ParentRepLocked , and Zoltar fork events; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists migrateRepToZoltar(securityPool, outcomeIndices) Anyone Migration proxy exists and the pool is PoolForked . Only a positive migration amount with at least one selected outcome checks the eight-week window, existing child ForkMigration state, outcome validity, and cumulative split bound. A zero amount skips those checks even when outcome values are supplied. For a positive migration amount and nonempty list, ensures that the forker's recorded pool migration amount has been split into each selected child REP branch. A zero migration amount or empty list returns after the proxy and pool-state guards without per-outcome validation or events. MigrationRepSplit and ChildRepSplit when a selected branch requires a new split; no event for a zero amount, empty list, or already-satisfied branch createChildUniverse(securityPool, outcomeIndex) Anyone Parent in migration window; selected fork outcome is well formed; child pool is not already deployed. The returned auction is nonzero, deployed, and has never been trusted by this forker; the child's fork-data slot is unused; and the child reports the expected parent, universe, source factory, forker, and auction. The selected child's reported nonzero escalation game passes the child-game trust boundary . These relationship checks do not independently prove configured-factory registration. Loads an already deployed child universe and REP token or deploys them when absent, then lazily deploys the selected child pool, coordinator, and auction; authorizes and links the child; captures and validates the child's escalation game; and initializes any continuation snapshot and materializes or sweeps child backing through that validated game. DeployChild only when child REP was absent; always SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , and TotalRepBackingUnitsSet ; AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as continuation and backing state requires migrateVault(securityPool, outcomeIndex) Vault owner for their non-escrowed position Migration window open; the selected child's reported nonzero escalation game passes the child-game trust boundary . The optional unresolved parent escalation-deposit accounting cleanup wrapper calls this function first to migrate transferable vault state. Converts the caller's parent REP backing-unit claim to REP at the fork snapshot and credits that REP amount as child-local backing units; transfers REP-denominated capacity ownership, latest-positive-deposit target preference metadata, and vault bad debt into one child pool; checkpoints but retains claimable fees in the parent vault; and separately routes proportional pool-level settlement collateral while preserving aggregate bad debt. Repeat calls can have no additional REP backing units, capacity ownership, or vault bad debt to move. VaultBadDebtMigrated and VaultMigrationCheckpoint migrateVaultWithUnresolvedEscalation(securityPool, vault, childOutcomeIndex) The named vault owner Migration window open; caller equals vault ; selected child not already recorded for this optional cleanup; the selected child's reported nonzero escalation game passes the child-game trust boundary . First runs ordinary migration for the same vault, which may convert its parent REP backing-unit claim to REP and credit that REP as child-local backing units; transfer capacity ownership, latest-positive-deposit target preference metadata, and vault bad debt to the selected child while preserving aggregate bad debt; checkpoint but retain claimable fees in the parent vault; and separately route proportional pool-level settlement collateral. It returns the selected child and its captured, validated escalation game to the unresolved-accounting cleanup phase, which reuses those exact addresses without reading the child's game again. The cleanup then clears that vault's unresolved parent escalation-deposit accounting in constant-size work and records it; the cleanup neither funds dispute-staked REP backing nor authorizes carried proofs. Vault migration events, including VaultBadDebtMigrated , plus EscalationMigrationEntitlementInitialized on first export and EscalationMigrationEntitlementMaterialized for the selected child claimForkedEscalationDeposits(...) The named vault owner Caller equals vault ; unresolved escalation existed when the pool initiated its own fork and the parent game still satisfies canTriggerOwnFork() by having either a local non-decision or an inherited threshold tie without a fixed outcome; selected child can be created or loaded, remains in ForkMigration , has a continuation game that passes the child-game trust boundary , and is inside the eight-week claim window. A nonempty list additionally requires the matching winning outcome, unclaimed deposit identities, and every deposit to commit vault as its immutable depositor. First gets or lazily deploys the selected child universe, REP token, pool, coordinator, and auction, then captures and validates the child's escalation game and uses that same game for continuation backing and escrow payment. A nonempty list claims winning own-fork parent deposits and records their stable identities against descendant replay. An empty list still performs child setup and emits a zero-valued claim summary. DeployChild , SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , TotalRepBackingUnitsSet , AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as setup requires; per claimed deposit, CarryDepositConsumed and ClaimDeposit ; escrow record/export events when REP is paid; always ClaimForkedEscalationDepositsToWallet , including for an empty list startTruthAuction(securityPool) Anyone Child migration window ended; pool is in fork migration; required child REP is available. If unresolved escalation existed at fork, any game reported during immediate completion passes the child-game trust boundary . Copies the frozen parent's remaining economic claim supply into the child, closes migration accounting, and either reopens a fully backed child or starts its repair auction. ShareTokenSupplySet and TruthAuctionStarted ; immediate no-auction completion also emits TruthAuctionFinalized , pool accounting checkpoints, and ForkContinuationResumed for an unresolved continuation finalizeTruthAuction(securityPool) Anyone Truth auction started, its one-week window has passed, msg.value is zero, and migrated collateral plus accepted bid ETH does not exceed current price-converted minting capacity. If unresolved escalation existed at fork, the game reported at completion passes the child-game trust boundary . Finalizes the ended auction, accounts migration-routed settlement collateral plus accepted bid ETH, and records every unmigrated REP backing unit, capacity unit, and proportional bad debt in an explicit nonwithdrawable unassigned position. It activates the child, fixes bidder REP-backing-unit and capacity-ownership rates, and saves the fee index. Positive-purchase auction ownership becomes fee eligible immediately; after a zero-purchase auction, the unassigned capacity remains outside fee eligibility. A nonzero repair contribution is rejected. TruthAuctionFinalized , auction AuctionFinalized , and pool accounting checkpoints; TruthAuctionHaircutApplied when purchased REP removes a positive escalation allocation; ForkContinuationResumed for an unresolved continuation settleAuctionBids(securityPool, vault, claimTickIndices, refundTickIndices) Anyone on behalf of the named bidder vault At least one index; before finalization the claim list must be empty and refund indexes must be eligible; after finalization all indexes must belong to the named vault owner and remain unsettled. Before finalization, refunds only provably losing bids. After finalization, combines claim and refund indexes into one settlement withdrawal and transfers each claim's proportional REP backing units, capacity ownership, bad debt, and finalization-to-claim fees from the unassigned position to the bidder vault. Capacity and bad-debt division dust follows each bid's deterministic cumulative ETH position, so claim order cannot change individual or aggregate settlement. The transfer does not change total capacity, fee eligibility, active open interest, total bad debt, retention, or aggregate accrued fees. A winning dust bid may receive capacity ownership even when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion. Underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited claimAuctionProceeds(securityPool, vault, tickIndices) Anyone on behalf of the named bidder vault Auction finalized. A nonempty list additionally requires every index to belong to the named vault owner and remain unsettled. For a nonempty list, withdraws finalized bid settlements and transfers each claim's proportional REP backing units, capacity ownership, bad debt, and finalization-to-claim fees from the unassigned position to the bidder vault. Capacity and bad-debt division dust follows each bid's deterministic cumulative ETH position, so claim order cannot change individual or aggregate settlement. The transfer does not change total capacity, fee eligibility, active open interest, total bad debt, retention, or aggregate accrued fees. A winning dust bid can receive positive capacity ownership when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion, so recipient code cannot block the subsequent credit. For an empty list, the underlying auction withdrawal returns four zeros and the wrapper exits after the finalization guard without validating bids or the named beneficiary, calling it, changing state, or emitting events. For processed bids, underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited; no event for an empty list initializeChildForkedEscalationGameIfNeeded(parent, child, childEscalationGame) This SecurityPoolForker contract only, through its migration delegate callback External caller is the forker itself; parent and child match the active migration path; a supplied nonzero game passes the child-game trust boundary . Allows delegated migration code to initialize a child continuation while preserving the forker as the authoritative caller and the already captured child-game identity. When unresolved escalation requires a continuation and no game existed, it captures and validates the game created by initialization before any continuation use. ChildDisputeStakedRepMaterialized and escalation-continuation events when initialization is required Direct ETH transfer to receive() A child-pool truth auction trusted by this forker during ChildPoolLinked trustedAuctionAddresses[msg.sender] was set when the forker linked the child and emitted ChildPoolLinked ; configured-factory registration determines whether that lineage is canonical. Accepts auction ETH during forker-controlled auction finalization. No dedicated receive event; auction AuctionFinalized is followed by forker TruthAuctionFinalized and pool accounting checkpoints","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"escalationgame","heading":"EscalationGame","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Escrows outcome REP, raises the running resolution cost, detects non-decision, and settles local or carried deposits. Source Read surface: Base getters are securityPool , repToken , activationTime , nonDecisionThresholdAttoRep , startBondAttoRep , nonDecisionTimestamp , nonDecisionState , forkContinuation , forkElapsedAtStart , forkResumedAt , fixedQuestionOutcome , nodes , disputeStakedRepByVaultAttoRep , totalDisputeStakedAttoRep , truthAuctionRepBeforeAttoRep , truthAuctionRepRemainingAttoRep , cumulativeClaimRetention , and cumulativeClaimRetentionExponent . The claim delegate fallback exposes rootClaimSourceGame , applyInheritedClaimRetention , and applyInheritedSourceStorageBasis . The source-storage-basis read allocates retained carry by cumulative-prefix differences so leaf allocations sum to the aggregate checkpoint. disputeStakedRepByVaultAttoRep is locally attributed current-game escrow used for health; inherited carry remains aggregate commitment state until proof settlement. Use previewDepositOnOutcome , computeIterativeAttritionCostAttoRep , computeTimeSinceStartFromAttritionCostAttoRep , totalCostAttoRep , getEscalationGameEndDate , getQuestionResolution , getFinalQuestionResolution , hasReachedNonDecision , canTriggerOwnFork , getBindingCapitalAttoRep , getOutcomeBalancesAttoRep , getDepositsByOutcome , getDepositsByOutcomeLength , forkCarrySnapshotInitialized , getOutcomeState , getForkCarrySnapshot , getForkCarryRoots , isForkCarryFundingComplete , getCarryLeafPageByOutcome , getProofConsumedCarriedDepositIndexesByOutcome , getLocalUnresolvedPrincipalByVaultAndOutcome , and getForkedEscrowByVaultAndOutcome for calculations, lifecycle authorization, pages, carry state, and escrow. Ordinary users route deposits and withdrawals through SecurityPool . Transaction Caller Main prerequisites State or asset effect Primary signals start(startBondAttoRep, nonDecisionThresholdAttoRep) EscalationGameFactory contract during atomic deployment Game not already started; threshold exceeds the positive start bond. Positive attoREP values are valid. Initializes a local game and sets activation three days after deployment. For ordinary pool games, the factory lowers an oversized configured bond to nonDecisionThresholdAttoRep - 1 before this call. GameStarted startFromFork(startBondAttoRep, nonDecisionThresholdAttoRep, elapsedAtFork, fixedQuestionOutcome, winnerHaircutPaidByFork, forkCarryInitialBackingAttoRep) Immutable owner ( EscalationGameFactory ) during atomic continuation deployment Game not started; threshold exceeds the positive start bond; inherited elapsed time is no greater than seven weeks. Positive attoREP values are valid. Initializes a paused continuation with inherited elapsed time, an optional fixed matching child outcome, and immutable fork-time haircut/backing accounting. It does not start the remaining clock until resumeFromFork . GameContinuedFromFork resumeFromFork() Owning SecurityPool only Fork-continuation mode; not previously resumed; immutable carry snapshot installed; aggregate REP funding complete. An unrelated fork requires one-to-one backing of effective unresolved principal. For an own-fork continuation, recorded initial backing must be at least sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , where sourcePrincipalAtForkAttoRep is the aggregate raw unresolved principal installed by the snapshot before effective direct-claim deductions. The live balance must cover that initial backing minus child REP already exported by valid direct pre-resume claims. Records the resume timestamp once the immutable carry commitment is installed and funded. The new deadline is max(rebasedCurveEnd, forkResumedAt + 3 days) , so even an exhausted inherited clock receives a fresh response period. After that deadline, getFinalQuestionResolution returns the fixed outcome when the continuation has one. ForkContinuationResumed applyTruthAuctionHaircut(repToRemove) The child pool's SecurityPoolForker only Paused fork continuation; no prior auction haircut; the requested amount is below the game's live REP balance. Transfers the sold child REP to the pool, applies one retention ratio to escrow and outcome balances, and rebases elapsed curve time. The fork remains final and the game remains paused until the pool resumes it. TruthAuctionHaircutApplied and REP Transfer recordDepositFromSecurityPool(...) Owning SecurityPool only Explicit non-decision state is None ; game unresolved; valid outcome; preview and accepted cumulative amount match; room remains below threshold. Appends an accepted local deposit, updates outcome and vault escrow, and records its carry leaf. LocalDepositAppended , DepositOnOutcome , optionally NonDecisionReached withdrawDeposit(uint256 depositIndex, outcome) Owning SecurityPool only Explicit non-decision state is None ; non- None supplied outcome; game final; game and pool final outcomes match; valid unsettled local deposit index. Consumes one local deposit after resolution. A winner pays the deposit's immutable depositor after its haircut; a loser only retires its escrow accounting. CarryDepositConsumed and VaultEscrowUpdated ; for a winner, ClaimDeposit , positive REP payout Transfer , and haircut burn signals when nonzero initializeForkCarrySnapshotWithResolutionBalances(...) Owning SecurityPool only Fork-continuation mode; no prior snapshot; each leaf count fits the MMR; supplied nonzero snapshot ID equals the hash of the normalized data. Installs the immutable inherited peaks, leaf counts, carry totals, resolution balances, and normalized nullifier roots; zero snapshot ID selects the computed ID. Two or more threshold-full inherited balances set nonDecisionState to InheritedThresholdTie without creating a local timestamp. ForkCarryCheckpoint ; additionally InheritedThresholdTie when the installed balances meet the non-decision threshold claimDepositForWinning(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome and valid unsettled local deposit with sufficient escrow. This entrypoint itself does not check final resolution or that the supplied outcome won; its trusted caller selects that path. Consumes a selected local deposit as a winner, consumes its vault escrow, burns the computed haircut when nonzero, and transfers the remaining positive REP payout to the deposit's immutable depositor. CarryDepositConsumed , VaultEscrowUpdated , ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when their amounts are positive claimDepositForWinningWithoutTransfer(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Valid in-range supplied outcome and unsettled local deposit with sufficient escrow. Unlike the transferring form, it has no explicit non- None guard; neither form checks final resolution or that the outcome won. Consumes a selected local deposit and its vault escrow. The depositor's raw escrow backing decreases by the inverse-retention claim units corresponding to the deposit's original principal: the principal itself with no local auction checkpoint, or ⌈originalPrincipal × truthAuctionRepBeforeAttoRep / truthAuctionRepRemainingAttoRep⌉ after a local haircut. Other unconsumed deposits by the same depositor remain backed. The game returns the computed winner amount to the trusted caller but deliberately neither transfers REP nor burns the computed haircut. CarryDepositConsumed , VaultEscrowUpdated , and ClaimDeposit with transferredRep = false ; no REP transfer or haircut burn exportUnresolvedDeposit(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None outcome and a valid unsettled local deposit. Final resolution is not required. Returns deposit identity and amount to the trusted caller while consuming the local deposit from unresolved/escrow accounting without transferring REP. CarryDepositConsumed and VaultEscrowUpdated ; no ClaimDeposit or REP transfer withdrawDeposit(CarriedDepositProof proof, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome; game final and matching the pool final outcome; supplied outcome is the winner; parent deposit was not directly claimed; valid unconsumed Merkle/nullifier proof. Consumes an inherited proof, transfers any positive winning payout, and burns the positive haircut unless the fork already paid it. CarryDepositConsumed and ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when positive exportVaultUnresolvedTotals(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. There is no explicit nonzero-receiver guard: a zero receiver succeeds when the total is zero but the token rejects it when a positive transfer is attempted. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, consumes aggregate unresolved and escrow accounting when positive, and transfers the positive total to repReceiver . Always VaultUnresolvedTotalsExported , including when every amount is zero; VaultEscrowUpdated and REP Transfer only for a positive total exportVaultUnresolvedTotalsWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, and consumes aggregate unresolved and escrow accounting when positive, but leaves token movement to its caller. Always VaultUnresolvedTotalsExported with transferredRep = false , including when every amount is zero; VaultEscrowUpdated only for a positive total; no REP transfer drainAllRep(receiver) Owning SecurityPool only receiver is nonzero; no positive-balance requirement. The protocol reaches this call from the owning pool after activateForkMode enters PoolForked . Transfers the game's full REP balance to receiver . A zero balance returns zero without a transfer or event. REP Transfer for a positive balance; no event at zero balance recordForkedEscrowForOutcome(depositor, outcome, sourcePrincipalAttoRep, childRepAmountAttoRep) Owning SecurityPool or its SecurityPoolForker Outcome is not None ; depositor is nonzero. Source principal and child REP may independently be zero; when both are zero, the call is a no-op. Accumulates source principal and child REP escrow for the depositor and outcome. The depositor remains the immutable payout owner; inherited claims remain in the carry commitment and are not copied into child-local ownership state. When both amounts are zero, returns without changing state or emitting an event. ForkedEscrowRecorded for a nonzero record; no event when both amounts are zero exportForkedEscrowByOutcome(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault and repReceiver are nonzero. Marks every remaining per-outcome escrow amount exported and transfers its positive child REP. When all outcomes were already empty or exported, returns zero arrays without state change, token transfer, or event. ForkedEscrowExported when any source principal or child REP remains; REP Transfer when positive child REP is transferred; no event for an already-empty export exportForkedEscrowByOutcomeWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero. Marks every remaining per-outcome escrow amount exported without transferring child REP. When all outcomes were already empty or exported, returns zero arrays without state change or event. ForkedEscrowExported with transferredRep = false when any source principal or child REP remains; no REP transfer; no event for an already-empty export sweepResidualRepToSecurityPool() Anyone Final outcome; no unresolved principal; no vault escrow; positive residual balance. Returns ordinary-game residual REP to the owning pool. Burns fork-continuation residual so pre-child capital cannot accrue to late or nonexistent child owners. ResidualRepSweptToSecurityPool for an ordinary game; ForkContinuationResidualRepBurned for a fork continuation","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"liquidationapprovalregistry","heading":"LiquidationApprovalRegistry","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores coordinator-local, bounded authorization for a receiver vault to accept liquidation debt from an exact operator. Source Read surface: Use coordinator to identify the validating coordinator and implied security pool. LIQUIDATION_APPROVAL_TYPEHASH , DOMAIN_SEPARATOR , and liquidationApprovalDigest define the chain- and registry-bound EIP-712 message. getLiquidationApproval reports parameters plus available, reserved, consumed, and revoked state; minimumLiquidationApprovalNonce reports receiver invalidation state; liquidationReservations and minimumHealthFactorBps expose operation reservation state and its execution-time health floor. Transaction Caller Main prerequisites State or asset effect Primary signals initialize(coordinator) Anyone while the registry remains uninitialized; normal factory deployment initializes the clone atomically Coordinator is nonzero and the registry has not been initialized. Binds this registry clone to one coordinator and therefore one security pool. No event; the public coordinator getter records the binding. setLiquidationApproval(params) The receiver vault named by params Correct local pool; nonzero receiver and operator; positive cumulative and per-operation limits with per-operation no greater than cumulative; health factor at least 10,000 BPS; live ordered validity window; unused, non-invalidated nonce. Installs explicit onchain bounded approval state and consumes the receiver-scoped nonce. LiquidationApprovalSet permitLiquidationApproval(params, signature) Anyone relaying the receiver vault signature Signature is valid for params.receiverVault ; the chain ID, registry address, stable name/version, pool, receiver, operator, target scope, limits, health factor, window, and nonce are bound by the digest; direct-install validation rules also pass. Validates an EIP-712 EOA or ERC-1271 signature immediately, installs explicit approval state, and consumes the receiver-scoped nonce. LiquidationApprovalSet revokeLiquidationApproval(approvalId) Approval receiver vault only Approval exists and is not already revoked. Prevents new reservations while leaving reservations already attached to staged operations intact. LiquidationApprovalRevoked with available, reserved, and consumed totals invalidateLiquidationApprovalNonce(newNonce) Receiver vault invalidating its own older nonce range New nonce is greater than the receiver current minimum. Raises the minimum nonce accepted for new approval installation or reservation. LiquidationApprovalNonceInvalidated reserve(operationId, approvalId, receiverVault, targetVault, operator, requestedDebtAttoEth, snapshotTargetDebtAttoEth, latestExecutionTimestamp) Bound coordinator only Approval matches local pool, receiver, exact operator, and exact or wildcard target; it is active, unrevoked, non-invalidated, valid through latest execution, and has positive reservable quota. Moves quota from available to pending reserved at staging, bounded by requested debt, target snapshot debt, per-operation limit, and available cumulative quota. LiquidationApprovalReserved release(operationId) Bound coordinator only Coordinator terminal cleanup path. Returns an unsettled delegated reservation to available quota. A missing, self-route, or already settled reservation is a no-op. LiquidationApprovalReleased when quota is returned consume(operationId, debtMovedAttoEth) Bound coordinator only For a delegated reservation, it is unsettled and moved debt does not exceed reserved debt. A self route is a no-op. Permanently consumes exactly moved debt, releases unused reservation, and settles the reservation once. LiquidationApprovalConsumed","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"openoraclepricecoordinator","heading":"OpenOraclePriceCoordinator","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup. Source Read surface: Configuration getters are MAX_PENDING_SETTLEMENT_OPERATIONS , OPEN_INTEREST_DIVIDER , reputationToken , securityPool , openOracle , weth , liquidationApprovalRegistry , gasConsumedOpenOracleReportPrice , gasConsumedSettlement , gasUnitsForOneDispute , initialReportPriorityFeeAttoEthPerGas , targetPriceErrorForDispute , openOracleSecurityMultiplierBps , settlementTime , disputeDelay , protocolFee , feePercentage , multiplier , timeType , trackDisputes , protocolFeeRecipient , escalationHaltMultiplierBps , maxSettlementBaseFeeMultiplierBps , and minLiquidationPriceDistanceBps . Current report and operation getters are pendingReportId , pendingReportSponsor , pendingOperationSlotId , lastSettlementTimestamp , lastPrice , pendingReportMaxSettlementBaseFeeAttoEthPerGas , stagedOperationCounter , and stagedOperations . Use isPriceValid , minimumToken1ReportAttoEth , getRequestPriceCostAttoEth , getQueuedOperationCostAttoEth , getSettlementCallbackGasLimit , getPendingOperationSlot , getActiveStagedOperationCount , getActiveStagedOperations , getPendingSettlementOperationCount , and getPendingSettlementOperationIds for derived or paged state. Report and staged-operation liveness depends on A16 timely inclusion , A17 corrector capability , A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . When lastPrice is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path. Transaction Caller Main prerequisites State or asset effect Primary signals requestPriceIfNeededAndStageLiquidation(targetVault, receiverVault, requestedDebtAttoEth, approvalId, ...) Liquidation operator; a delegated receiver must have approved this exact operator Receiver differs from target; delegated approval matches pool, receiver, operator, and target scope, has available cumulative and per-operation quota, and remains valid through latest execution. Stages explicit operator, receiver, and target roles and reserves bounded receiver quota before any oracle work. The self-receiving operator path uses a zero approval ID. LiquidationRouteStaged ; LiquidationApprovalReserved on a delegated route; staged-operation lifecycle events requestPriceIfNeededAndStageOperation(...) with funding when stale Vault owner for self withdrawal; legacy self-receiving liquidation callers remain supported. While a report is pending, only that report sponsor may stage more operations. securityPool.isEscalationResolved() is false; valid target, nonzero amount, and timeout from 1 second through 5 minutes. Bounty, buffered report funding, matching REP, and token approvals are required only when this call opens a new report. The caller must accept any positive unused-ETH refund. Records the operation, executes immediately with a fresh price, or attaches it to a bounded pending settlement batch and opens a report when required. If unused ETH is positive, the final caller refund uses a low-level callback; rejection rolls back the entire transaction, including any queueing, immediate execution, or newly opened report. StagedOperationQueued , possibly PriceRequested , then ExecutedStagedOperation ; authoritative CoordinatorStateCheckpoint records requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) with report funding Anyone when no fresh price or report is pending Cached price stale; no pending report; nonzero proposed REP/ETH price, ETH bounty, and funding and approvals for at least the configured priority report plus the larger of the base-fee and open-interest WETH reports, plus matching REP. Zero requested WETH uses the minimum; a larger request voluntarily increases the initial report. The caller must accept any positive excess-ETH refund. Opens and atomically funds a fresh WETH/REP report without staging a new operation, then refunds any positive excess ETH through a low-level caller callback. Callback rejection rolls back the report and initial position. PriceRequested and CoordinatorStateCheckpoint executeStagedOperation(operationId) Anyone Operation exists. Expired cleanup requires no valid price; a non-expired operation requires a fresh coordinator price. Lifecycle failures are emitted rather than retried. Consumes an expired operation and releases its delegated reservation without requiring a valid price. Otherwise, consumes and attempts the active operation using the current fresh price. Price-report funding is independent of the operation's notional; the downstream operation applies its own protocol bounds. ExecutedStagedOperation , either LiquidationApprovalConsumed or LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint expireStagedOperation(operationId) Anyone Operation exists and its settlement-plus-validity window has elapsed. Permissionlessly consumes an expired operation and releases its liquidation reservation without requiring a valid oracle price. ExecutedStagedOperation , LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint recoverSettledPendingReport() Anyone A pending report ID exists and its stored OpenOracle storedGame(reportId).settlementTimestamp is nonzero. Clears a pending report whose normal callback path did not clear coordinator state, consumes every live operation attached to that report, and releases each delegated-liquidation reservation. Operations that were active but outside the bounded pending callback batch remain active. PendingReportRecovered , failed ExecutedStagedOperation for each live attached operation, LiquidationApprovalReleased for each attached delegated liquidation, and CoordinatorStateCheckpoint openOracleCallback(...) Configured OpenOracle only Callback report matches the pending report; excessive settlement basefee, a saturated uint24 report counter, an uneconomic final history record at its recorded base fee plus configured priority fee, or zero values reject the price after clearing pending report state. A valid settlement updates the price and auto-executes the bounded pending batch. A terminally rejected settlement consumes the pending batch and releases every liquidation reservation. PriceReported or PriceReportRejected ; operation execution events; authoritative CoordinatorStateCheckpoint records setLiquidationApprovalRegistry(registry) Coordinator deployment factory only Registry is nonzero and no registry was previously installed. Binds the coordinator-local approval registry once. No event; deterministic factory deployment and the public getter identify the registry. setSecurityPool(pool) Anyone while securityPool remains zero; normal factory deployment calls atomically Current securityPool is zero; the argument itself is not required to be nonzero. A nonzero value binds the pool permanently. A zero value emits and checkpoints zero but leaves the setter callable. Normal factory deployment supplies the nonzero canonical pool before returning the coordinator. SecurityPoolSet and CoordinatorStateCheckpoint setRepEthPrice(price) Configured nonzero SecurityPool only Caller equals the configured pool. Seeds the coordinator's price value, including zero, for inherited child state. RepEthPriceSet and CoordinatorStateCheckpoint","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"sharetoken","heading":"ShareToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores universe-aware ERC-1155 outcome shares and materializes a holder's persistent source entitlement in selected fork branches. Source Read surface: Base and relationship getters are name , symbol , zoltar , canonicalPoolByUniverse , _balances , _supplies , and _operatorApprovals . Standard ERC-1155 reads are supportsInterface , balanceOf , totalSupply , balanceOfBatch , and isApprovedForAll ; protocol-specific reads are isAuthorized , totalSupplyForOutcome , maximumOutcomeSupply , balanceOfOutcome , balanceOfShares , getMigratedShareAmountAttoShares , getTokenId , getTokenIds , and unpackTokenId . Transaction Caller Main prerequisites State or asset effect Primary signals setApprovalForAll(operator, approved) Any token account setting its own operator approval The operator differs from the caller. Sets or clears the operator's authority over all of the caller's outcome-token balances. ApprovalForAll Both safeTransferFrom(...) overloads Share holder or approved ERC-1155 operator Caller holds the source balance or has operator approval; the source account has not materialized that token into any child branch; destination is nonzero; the source balance is sufficient; under A22 asset-recipient compatibility , a contract recipient accepts the ERC-1155 callback. Transfers one outcome-token balance without changing supply. TransferSingle Both safeBatchTransferFrom(...) overloads Share holder or approved ERC-1155 operator for a nonempty batch; any caller for an empty batch ID and value array lengths match. A nonempty batch also requires holder or operator authority, no listed source token that the source account has already materialized into a child branch, a nonzero destination, sufficient source balances, and, under A22 asset-recipient compatibility , an accepting ERC-1155 callback from a contract recipient; the empty-batch no-op performs none of those checks. A nonempty batch transfers each listed outcome-token balance without changing supply. Equal empty ID and value arrays return as a no-op without an event. TransferBatch for a nonempty batch; no event for an empty batch migrate(fromId, targetOutcomeIndexes) Holder of the source token ID Source universe forked; canonical source pool is Operational or PoolForked , and an Operational source has no inherited fixed outcome because auto-fork activation rejects one; positive source balance; nonempty, strictly increasing, well-formed outcomes; every target in a multi-target call already has a canonical child pool; after the branch-creation window, a single target must also already exist; at least one selected child has an unmaterialized balance; under A22 asset-recipient compatibility , a contract holder accepts onERC1155Received for every target mint. If needed, first freezes the operational source pool and records its fork snapshot. A single-target call may lazily create that child while the branch-creation window is open. It keeps and locks the holder's source entitlement, then mints each selected child-universe token ID up to the current source balance. Later source additions materialize only the unminted delta. A contract holder receives the ERC-1155 single-receiver callback for each mint; rejection rolls back the mint and preceding fork or child setup. PoolForkModeActivated , PoolAccountingCheckpoint , SecurityPoolForkSnapshot , ParentRepLocked , and optionally DisputeStakedRepDrainedAtFork when auto-forking; SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , and ChildPoolLinked when lazily deploying, plus DeployChild , ChildRepSplit , PoolHeldRepSweptToChild , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , and ChildDisputeStakedRepMaterialized as applicable; then one ERC-1155 mint TransferSingle and Migrate per materialized target on successful callbacks authorize(securityPoolCandidate) Initially authorized SecurityPoolFactory for an origin pool; an authorized parent SecurityPool for a child pool Caller is already authorized; the candidate reports this exact share token; its universe has no different canonical pool. Establishes the candidate as canonicalPoolByUniverse for its universe and adds it to the set allowed to mint, burn, and authorize descendants. Reauthorizing the same candidate is a no-op. AuthorizationUpdated on first authorization; no event when the same candidate is already authorized mintCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero; amount is positive; under A22 asset-recipient compatibility , a contract account accepts onERC1155BatchReceived . Mints amount each of Invalid, Yes, and No to account , then invokes its ERC-1155 batch-receiver callback when it is a contract. Rejection rolls back the mint and the authorized pool's surrounding transaction. TransferBatch on a successful callback burnCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero and has at least amount of every outcome. Burns amount each of Invalid, Yes, and No from account ; global outcome supplies may differ. TransferBatch burnTokenIdAndGetRemainingSupply(tokenId, account) An authorized SecurityPool account is nonzero; caller is authorized. Burns account 's full balance of tokenId and returns the burned amount and that token ID's remaining supply. TransferSingle , including when the burned balance is zero","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"uniformpricedualcapbatchauction","heading":"UniformPriceDualCapBatchAuction","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Collects ETH bids under ETH-raise and REP-sale caps, computes one clearing result, and supports paged settlement. AVL, cumulative-allocation, and refund-prefix mechanics live in UniformPriceDualCapBatchAuctionStorage , an internal storage library. Source Read surface: Auction summary getters are maxAttoRepBeingSold , attoEthRaiseCap , finalized , clearingTick , ethFilledAtClearingAttoEth , attoEthRaised , totalAttoRepPurchased , auctionStarted , minBidSizeAttoEth , owner , underfunded , underfundedThreshold , underfundedWinningAttoEth , and activeTickCount . pendingEthRefundsAttoEth reports ETH whose gas-bounded push failed during settlement and can still be pulled. Use computeClearing , previewFinalization , tickToPrice , getTickSummary , getTickCount , getTickPage , getActiveTickPage , getBidCountAtTick , getBidPageAtTick , getBidderBidCount , and getBidderBidPage before finalizing or submitting settlement indexes. Transaction Caller Main prerequisites State or asset effect Primary signals startAuction(attoEthRaiseCap, maxAttoRepBeingSold) Auction owner ( SecurityPoolForker ) only Auction not previously started; both caps are positive; the REP cap does not exceed 11 million REP; the ETH cap fits in uint128 ; the block timestamp fits in uint48 . Starts the one-week auction and fixes its two caps and minimum bid. AuctionStarted submitBid(tick) with ETH Any bidder Auction active and unfinalized; before one-week deadline; bid meets minBidSizeAttoEth ; tick maps to nonzero price; the individual bid and the resulting cumulative ETH at that tick each fit in uint128 . Adds ETH demand at the selected positive-price tick while extending that tick's append-only cumulative bid and refund history, including when a fully refunded tick becomes active again. BidSubmitted refundLosingBids(tickIndices) Bidder for its own bids Auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to the caller and are strictly losing and unrefunded. A nonempty list marks the caller's bids already provably below the current clearing tick and attempts an immediate gas-bounded ETH refund. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails refundLosingBidsFor(bidder, tickIndices) Auction owner ( SecurityPoolForker ) only; public callers use settleAuctionBids Named bidder is nonzero; auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to that bidder and are strictly losing and unrefunded. A nonempty list marks and attempts a gas-bounded refund of a named bidder's bids already provably below the current clearing tick. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails finalize() Auction owner ( SecurityPoolForker ) only; users reach it through finalizeTruthAuction Auction started, not finalized, and one-week deadline reached; owner accepts the proceeds ETH call, including zero value. Fixes the clearing mode, clearing tick, ETH totals, and aggregate REP allocation, then calls the owner with the resulting proceeds, including when zero. A rejected call reverts finalization and its event. AuctionFinalized withdrawBids(withdrawFor, tickIndices, proRataTotal, secondaryProRataTotal) Auction owner only Auction finalized; caller is owner. Nonempty indexes belong to withdrawFor and remain unsettled. For a nonempty list, returns refunds, purchased REP, and two companion pro-rata allocations for the selected beneficiary bids so the forker can credit REP backing units, capacity ownership, and bad debt. Withdrawal-time allocation assigns division dust from deterministic cumulative ETH positions, making each payout independent of claim order. A rejected, reverted, or gas-exhausted positive refund push is gas-bounded and deferred rather than reverting or starving the REP, capacity-ownership, and bad-debt settlement. An empty list returns four zeros without changing bids, emitting events, or calling the beneficiary. BidSettled per processed bid; EthRefundDeferred when a positive push fails withdrawPendingEthRefund() Bidder with deferred ETH Caller has a positive pendingEthRefundsAttoEth balance and currently accepts ETH. Clears the caller's complete deferred refund and emits its withdrawal before transferring without the push-refund gas cap, so callback-created deferrals follow the clear in log order. A rejected pull reverts the transfer, clear, and event. PendingEthRefundWithdrawn","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"","heading":"","keywords":["invariants","safety","liveness","properties"],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"An invariant is a statement that must remain true at every successful external-call boundary, across every legal lifecycle transition, or under the economic assumptions named beside it. Reverts may interrupt a transition, but they must roll back all of its state and asset effects. The catalog separates local contract guards from cross-contract properties and economic assumptions. It also records launch-critical properties that the current contracts do not preserve. A documented implementation choice is not automatically a safe invariant. See the Protocol Security Model for assumptions A01–A28. Funded and underfunded clearing are explained in the Truth Auction clearing guide ; oracle notional and residual-loss analysis are explained in OpenOracle Integration . Four connected layers run from local authority guards through cross-contract conservation and lifecycle liveness to economic security. A failure in an earlier layer invalidates the safety claims built above it. Invariant Dependency Correct callers are necessary but insufficient. Their actions must conserve assets, every mandatory state transition must remain reachable, and the named participation and inclusion assumptions must hold where the protocol deliberately relies on economic incentives. Search Type Any type Enforcement status Any status Subsystem Any subsystem Expand visible Collapse all Reset filters Loading invariants… No invariants match the selected filters.","title":"Protocol invariants","topic":"Safety","weight":1},{"fragment":"standing","heading":"Classification and Scope","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Each entry separates the kind of property from its current enforcement status. A status describes the strongest claim supported by the local implementation and tests; it is not a formal proof. Changes to any contract named in the evidence column must preserve the full statement, not only the check performed in one function. Type Meaning Safety A forbidden caller, state, transition, or replay cannot succeed. Conservation Assets, liabilities, supplies, and claims reconcile without duplication or loss. Liveness A required permissionless action remains executable under the stated conditions. External assumption The property depends on participant behavior, inclusion, liquidity, or token behavior outside the contracts. Enforcement status Meaning Required response Contract guard A direct authorization, bound, or state check enforces the property locally. Keep the guard and test its negative boundary. Reviewed preservation Several contracts cooperate to preserve the property in reviewed flows and tests. Protect it with cross-contract invariant and lifecycle tests. Open violation A concrete sequence reaches a state that contradicts the required property. Fix before deployment and retain a regression test. Economic or external assumption Solidity cannot guarantee the property without honest participation, liquidity, or transaction inclusion. State and monitor the assumption, and document both its failure mode and the guarantees deliberately excluded from the protocol model. Proposed guard The property closes a missing safety or verification boundary not currently represented as one assertion. Add a contract check, model assertion, or invariant harness where practical. Invariant identifiers are stable review labels. They are grouped by authority, universe, question, asset, share, vault, fork, escalation, oracle, auction, lifecycle, and observability boundaries rather than by Solidity file.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"standing","heading":"Terms used in the catalog","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Pool question The question whose outcome determines the pool's winning shares and escalation payouts. Fork question The ended question recorded by the universe fork. It may differ from the pool question. Child branch A well-formed answer encoding for the fork question, used to derive one child universe from its parent. SecurityPool's binary pool-question paths specifically use Invalid, Yes, and No. Continuation game A child escalation game initialized from an unresolved parent game's fork snapshot. Fixed payout outcome The pool-question outcome a continuation game must use after its deadline, regardless of its copied balance leader. Direct fork A caller invokes Zoltar's universe-fork entrypoint directly rather than through a pool's own non-decision path. Recursive fork A fork of a child universe created by an earlier fork. Tracked balance An amount recorded by protocol accounting; it excludes unsolicited assets unless a named transition explicitly credits them. Consumed claim A deposit, bid, or proof that has already been settled, refunded, exported, or otherwise made unusable.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"zoltar-invariants","heading":"Zoltar Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern core universe creation, REP supply, universe forks, deterministic child identities, and branch-specific REP migration.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"universes","heading":"Universes, REP Supply, and Migration","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"UNI-01 One fork per universe Required property An initialized universe records at most one fork time and one fork question. Example: After a universe forks on question 17, a second call attempting to fork it on question 22 reverts. Type Safety Enforcement status Contract guard Primary evidence Zoltar.forkUniverse UNI-02 Valid fork questions Required property forkUniverse accepts only a question that exists in ZoltarQuestionData and whose end time has been reached ( block.timestamp >= endTime ). A pool may separately require the fork question to equal its pool question. Zoltar intentionally imposes no question-creation age, universe registration, or pool-relevance condition. Example: At exactly a known question's end time, a caller may fork a universe even when that question came from another universe or was created with an already-past end time; an unknown or still-open question is rejected. Requiring a universe-relevant or sufficiently aged question is not a Zoltar invariant. Type Safety Enforcement status Contract guard Primary evidence forkUniverse , question model Related external boundaries A15 intended question selection UNI-03 Fork threshold burn and credit Required property The fork threshold is ⌊parent theoretical supply / forkThresholdDivisor⌋ . Starting a fork burns that amount of the initiator's parent REP, reduces the parent theoretical supply by the same amount, and credits the threshold minus ⌊threshold / forkBurnDivisor⌋ for migration. The configured forkBurnDivisor is at least 5 , so the uncredited haircut cannot exceed 20%. Example: With a 1,000 REP theoretical supply, threshold divisor 20, and burn divisor 5, the initiator commits 50 parent REP, pays a 10 REP haircut, and receives 40 REP of migration credit. Type Conservation Enforcement status Contract guard Primary evidence getForkThresholdAttoRep and forkUniverse UNI-04 Deterministic child universe IDs Required property A child universe identity is exactly the truncated hash of its parent universe and fork outcome; one parent-outcome pair maps to one child. Example: Repeatedly deriving the Yes child of the same parent returns the same universe ID, while deriving its No child returns a different ID. Type Safety Enforcement status Contract guard Primary evidence getChildUniverseId UNI-05 Well-formed child outcomes Required property Only well-formed outcomes of the recorded fork question can deploy child universes or receive migrated REP. Example: For a binary categorical fork question whose valid indexes are 0, 1, and 2, passing index 3 cannot deploy a child or receive child REP. Type Safety Enforcement status Contract guard Primary evidence deployChild and splitRepInternal UNI-06 Per-child migration mint limit Required property For one migrator and one child, cumulative child REP minted never exceeds that migrator's parent migration balance. Example: A migrator with 40 REP of migration credit cannot mint 25 REP and later mint another 20 REP into the same child. Type Safety Enforcement status Contract guard Primary evidence childMigrationRepAmounts UNI-07 Branch-specific child REP Required property One burned parent migration balance may mint the same credited amount into several selected children. Each minted balance belongs to a different child REP token, and migration never restores the burned parent REP. Example: A 40 REP migration credit may mint 40 Yes-child REP and 40 No-child REP, but neither balance is parent REP and the burned parent balance remains zero. Type Conservation Enforcement status Reviewed preservation Primary evidence splitRepInternal , ReputationToken UNI-08 Child theoretical supply snapshot Required property The child theoretical-supply snapshot equals the parent's theoretical supply after the threshold burn plus the initiator's post-haircut migration credit. Each child therefore excludes the uncredited fork haircut. Example: If a fork removes 50 parent REP and credits 40 migration REP, each child's theoretical snapshot is 10 REP below the pre-fork supply. Type Conservation Enforcement status Reviewed preservation Primary evidence childUniverseTheoreticalSupplySnapshotsAttoRep UNI-09 Child REP supply coherence Required property For each child REP token, total minted supply equals the sum of holder balances, never exceeds the child's theoretical-supply snapshot, and uses the same theoretical maximum recorded by the child universe. Example: If two migrators mint 12 and 7 child REP, the child token supply is 19 REP, both balances sum to 19 REP, and minting does not change the child's theoretical maximum. Type Conservation Enforcement status Reviewed preservation Primary evidence splitMigrationRep and child universe accounting , ReputationToken supply , aggregate child-supply invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"questions","heading":"Questions and Answer Classification","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"QST-01 Question registry and classifier coherence Required property Every successful question creation appends its deterministic ID exactly once and leaves prior question data and labels immutable. For every stored question and answer, the malformed classifier is true exactly when the public answer name is Malformed . Example: Appending a scalar question after two categorical questions preserves the first two records and their order; a scalar answer with reserved bits is both classified and named malformed. Type Safety Enforcement status Reviewed preservation Primary evidence createQuestion , getQuestions , and answer classification , registry and classifier invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"statoblast-invariants","heading":"Augur Statoblast Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern Statoblast deployment, SecurityPool accounting, fork migration, escalation, oracle operations, truth auctions, and the cross-contract lifecycle built on Zoltar universes.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"authority","heading":"Authority and Deployment Identity","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A23 verified deployments and A27 cryptographic identity . AUTH-01 Coordinator and forker-only operations Required property Only the immutable coordinator may execute price-sensitive pool operations, and only the immutable forker may mutate fork accounting or transfer migration assets. Example: A wallet calling withdrawRepFromVault directly is rejected because only the coordinator may call it. Type Safety Enforcement status Contract guard Primary evidence SecurityPool.onlyValidOracle and onlyForker AUTH-02 Atomic pool and coordinator binding Required property The pool and its coordinator bind once during atomic factory deployment; no external caller gets a transaction boundary in which it can install a substitute pool. Example: A caller cannot front-run factory deployment and make the coordinator point to an attacker-controlled pool. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , setSecurityPool AUTH-03 Immutable protocol logic Required property No mutable owner, governance address, upgrade implementation, or emergency operator can replace protocol logic after deployment. Example: After deployment, no administrator can point a pool proxy at different bytecode because the pool has no upgrade slot. Type Safety Enforcement status Reviewed preservation Primary evidence Immutable release posture AUTH-04 Deterministic deployment identity Required property Every canonical CREATE2 address commits to the correct factory, salt, constructor arguments, parent identity, universe, question, and multiplier. Example: Changing a child's question ID changes its derived address instead of deploying different semantics at the expected address. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , addressDerivation.ts AUTH-05 Delegate storage compatibility Required property Delegatecall targets are constructor-installed protocol modules, and every target interprets the forker's storage with the same layout. Example: The vault-migration delegate reads forkDataByPool from the same slot used by the forker rather than corrupting adjacent state. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolForker , interface and storage-layout regression tests AUTH-06 Permissionless settlement beneficiaries Required property Anyone may trigger permissionless settlement, but the caller cannot choose its beneficiary. Each transfer or REP-backing-unit credit goes only to the vault, bidder, depositor, or share holder recorded for that claim. Example: Alice may settle Bob's auction bid, but the purchased REP remains pool-held while the corresponding REP backing units and capacity ownership are credited to Bob's vault. Type Safety Enforcement status Contract guard Primary evidence Contract interaction reference AUTH-07 Published deployment verification Required property For every published deployment address, the runtime-bytecode hash and constructor-installed dependencies must match the reviewed release manifest. Example: A manifest check fails when the published coordinator address contains code built with a different OpenOracle dependency. Type Safety Enforcement status Proposed guard Primary evidence Deployment Status Oracle limits , mainnet manifest , Sepolia manifest AUTH-08 Factory registry and lineage bijection Required property Every factory deployment record names one unique pool whose runtime parent, share token, universe, and question match the record. Its pool-to-origin reverse lookup and origin/universe forward lookup are mutual inverses, and its recorded share token authorizes that pool. Example: Looking up a child pool's origin and then resolving that origin with the child's universe returns the same child, not a pool from an independent origin lineage in that universe. Type Safety Enforcement status Reviewed preservation Primary evidence factory registry and canonical lookups , share-token authorization , stateful registry-bijection test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"pool-accounting","heading":"Pool Assets, Shares, and Vaults","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"The raw ETH balance needs an explicit surplus term. Without it, forced ETH is indistinguishable from collateral and can change an exchange rate or lifecycle precondition. rawEthBalanceAttoEth = settlementCollateralAttoEth + unallocatedAccruedFeesAttoEth + totalClaimableVaultFeesAttoEth + explicitSurplusAttoEth Every successful external call should reconcile raw ETH into named liabilities or an explicit surplus bucket. All equation terms are denominated in attoETH; raw balance alone must not define collateral. BAL-01 ETH conservation Required property At every successful external-call boundary, raw ETH = tracked collateral + unallocated fee reserve + total claimable vault fees + unaccounted surplus . Surplus is the derived remainder and is never a protocol liability. Example: If the pool holds 12 ETH, owes 2 ETH in fees, and tracks 9 ETH of collateral, the remaining 1 ETH is surplus rather than additional collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence Pool ETH accounting , stateful exact-accounting invariant tests BAL-02 Unsolicited ETH isolation Required property Unsolicited ETH cannot change collateral, initialize an exchange rate, increase liabilities, or block a required transition. Example: ETH forced into an empty pool does not let the first complete-set mint use that ETH as its collateral base. Type Conservation Enforcement status Reviewed preservation Primary evidence receive , tracked collateral, and fee redemption , auction finalization BAL-03 Collateral capacity Required property At a complete-set mint boundary, the resulting tracked collateral must fit the current ETH minting capacity derived from total REP-denominated capacity ownership, the live REP-per-ETH price, and the pool security multiplier. Finalized auction ownership enters total ownership, and positive-purchase ownership also enters fee eligibility and accrues fees whether or not a bidder has claimed it. Unassigned auction ownership can provide additional minting headroom only when its explicit position remains healthy after the mint. Existing open interest is not deleted when later repricing lowers live capacity, so this is not a continuous collateral-below-capacity invariant. Fee checkpointing and redemption cannot reclassify unsolicited ETH as collateral, and a price change reprices capacity without iterating through vaults. Example: If current oracle-priced capacity is 10 ETH, a mint that would raise tracked collateral from 9 ETH to 11 ETH reverts even if the raw balance contains surplus ETH. A later REP price change can lower capacity below 9 ETH without changing the existing open interest or ownership records. Type Conservation Enforcement status Reviewed preservation Primary evidence _requireCapacityNotExceeded , setPoolFinancials , and fee redemption , fork finalization BAL-04 REP coverage boundaries Required property A REP withdrawal or escalation deposit must leave the affected vault and aggregate pool totals independently passing both live open-interest health branches. A delegated liquidation receiver must pass both branches after accepting debt; liquidation does not require the unhealthy target to become healthy. Subsequent repricing may make an existing position unhealthy without deleting its open interest, enabling liquidation rather than retroactively reverting the price change. The associated-REP branch may count locally dispute-staked REP; the free-REP branch counts only pool-held vault REP and uses at least the 10,500-BPS liquidation-award reserve. Required backing rounds upward. A delegated receiver's factor of 10,000 is exactly the protocol minimum, and any higher approved factor multiplies both branches at execution. Example: A receiver approved at 12,000 BPS must retain 1.2× the upward-rounded associated-REP requirement and 1.2× the upward-rounded free-REP requirement after accepting live debt. Passing only one branch is insufficient. Type Safety Enforcement status Contract guard Primary evidence Capacity ownership and withdrawal checks BAL-05 REP backing units conversion consistency Required property After deposits, withdrawals, liquidations, escalation transfers, migration, and auction claims, the same current pool-held REP balance and total REP backing units govern both REP-to-backing-unit and backing-units-to-REP conversion. Both proportional directions use full-precision multiplication and division so a representable claim cannot fail only because its intermediate product exceeds uint256 . Example: Depositing REP and immediately converting the resulting REP backing units back to REP uses the same denominator rather than a stale pre-deposit balance. Type Conservation Enforcement status Reviewed preservation Primary evidence attoRepToBackingUnits and backingUnitsToAttoRep BAL-06 Fee liability accounting Required property Every attoETH of accrued fees is represented once as unallocated reserve or claimable vault fees, and redemption clears the claimable fee balance before ETH is sent. Example: Assigning 3 attoETH of reserve to a vault decreases unallocated reserve by 3 and increases that vault's claimable fees by 3, without creating a second claim. Type Conservation Enforcement status Reviewed preservation Primary evidence Fee accumulator and redeemFees BAL-07 Fee reserve protection Required property Fork transfers and redemptions cannot spend ETH reserved for unpaid or unallocated fees. Example: A fork transfer may move 8 ETH of collateral from a 10 ETH balance that also contains 2 ETH of fee liabilities, but may not move all 10 ETH. Type Conservation Enforcement status Contract guard Primary evidence transferEth BAL-08 Aggregate pool-ledger coherence Required property totalClaimableVaultFeesAttoEth equals summed unpaid vault fees. In Operational , fee-eligible capacity ownership equals summed live vault capacity ownership plus sold truth-auction ownership awaiting claim. Uncheckpointed capacity ownership covers fee-eligible ownership not yet reconciled to the current global index, including an unclaimed auction allocation after the index advances. During ForkMigration , configured migrated capacity ownerships remain pending outside those pool aggregates. A PoolForked parent retains its fork-time total and fee-eligible snapshots as individual vaults migrate. After a positive-purchase truth-auction activation, total capacity ownership equals fee-eligible capacity ownership, including the outstanding unclaimed auction allocation in both aggregates. If the auction purchases zero REP, the unmigrated remainder remains explicit unassigned capacity but stays outside fee eligibility. At finalization and after every claim, every capacity unit counted by the child belongs either to a real vault or to that nonwithdrawable unassigned position. The auction settlement specification defines that zero-purchase allocation rule. Example: Migrating one of two vaults that each own 3 REP of capacity leaves the parent's frozen 6 REP total unchanged and records 3 REP as unassigned in the child. With purchased REP, finalization adds those 3 REP of sold ownership to fee eligibility immediately, and later claims only transfer the position to bidder vaults. With zero purchased REP, the same 3 REP remains system-owned and ineligible. Type Conservation Enforcement status Reviewed preservation Primary evidence getPoolAccountingSnapshot and vault checkpointing , truth-auction claim accounting , stateful ledger and claim-order invariant tests SHARE-01 Complete-set symmetry Required property A complete-set mint or burn changes Invalid, Yes, and No balances by the same amount in the same universe. Example: Minting five complete sets creates five Invalid, five Yes, and five No shares for that universe. Type Conservation Enforcement status Contract guard Primary evidence mintCompleteSets and burnCompleteSets SHARE-02 Complete-set redemption Required property Before outcome finalization, burning B complete sets returns ⌊B × tracked collateral / complete-set supply⌋ after the fee checkpoint, then reduces supply by B and collateral by the ETH returned. Example: With 100 sets and 10 ETH of tracked collateral, burning 10 sets returns 1 ETH before integer-rounding residue. Type Conservation Enforcement status Reviewed preservation Primary evidence redeemCompleteSet SHARE-03 Positive-output minting Required property A successful positive-value complete-set mint returns a positive share amount. Example: A 1 attoETH mint that rounds to zero shares reverts instead of accepting the ETH. Type Conservation Enforcement status Contract guard Primary evidence attoEthToAttoShares and createCompleteSet SHARE-04 Child supply denominators Required property Child setup copies the frozen parent's remaining economic claim supply as its collateral denominator. That supply includes both materialized child ERC-1155 balances and source entitlements that can materialize later. Complete-set minting adds claims, while complete-set and winning-share redemption consume them. Example: If a parent freezes with 10 claims per outcome and a child has no materialized shares yet, setup still records 10. A new minter receives only the shares purchased at that established rate, while all 10 source claims retain their reserved collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence attoEthToAttoShares and redeemShares and ShareToken.migrate SHARE-05 Winning-share payout cap Required property Total ETH paid to winning shares never exceeds the collateral available when redemption begins; rounding residue remains for later winning holders. Example: When two winners redeem sequentially, the second receives its fraction of the collateral left after the first rather than a fraction of the original balance paid twice. Type Conservation Enforcement status Contract guard Primary evidence redeemShares SHARE-06 Share supply conservation Required property For every ERC-1155 share ID, total supply equals the sum of holder balances through mint, transfer, and burn operations. Per-child materialization and duplicate prevention are owned by FORK-10 . Example: Moving two Yes shares between wallets leaves Yes supply unchanged; burning one Yes share reduces both the holder-balance sum and Yes supply by one. Type Conservation Enforcement status Reviewed preservation Primary evidence ERC-1155 supply accounting , modeled holder-supply invariant test VAULT-01 Dispute-staked REP isolation Required property REP escrowed in an escalation game cannot also be withdrawn, liquidated as pool-held vault REP backing, or migrated through the ordinary non-escrowed vault path. Example: A vault initially attributed 20 REP that dispute-stakes 5 REP is left with 15 REP of vault backing plus a separate 5 REP claim. It cannot make an ordinary withdrawal while that escrow remains; ordinary vault migration moves only the 15 REP backing, while the dispute-staked REP claim follows its separate migration path. Type Conservation Enforcement status Reviewed preservation Primary evidence Escrow checks , EscalationGame VAULT-02 Liquidation conservation and freshness Required property A liquidation transfers rather than creates aggregate REP backing units and capacity ownership, and total capacity ownership across target and receiver is conserved exactly. A nominal debt quote is bounded by the request, target open interest, and the amount whose complete REP award the target can fund. Proportional ownership is rounded downward; moved debt is the receiver's exact live open-interest increase and is bounded by that quote. A delegated route additionally bounds moved debt by its staged approval reservation; the self-receiving route has no approval reservation. On a full-target request, target-local bad debt is target open interest minus exact moved debt, so it may include an award-unfunded slice and integer-allocation residue. Because vault open interest is independently derived with upward rounding, exact before-and-after vault debt deltas are not a conservation identity. Unmatched ownership, escalation claims, accrued fees, and pool-held REP surplus remain with the target. Execution rejects changed target snapshots, enforces the receiver's live post-transfer health factor, and every delegated terminal path consumes or releases its reservation exactly once. Example: If the target or receiver changes after staging, execution may fail safely and release the full reservation. The operator receives no ownership merely for submitting the transaction. Type Conservation Enforcement status Reviewed preservation Primary evidence Liquidation design , performLiquidation , executeStagedOperation VAULT-04 Escalation claims are non-transferable and migration-neutral Required property An escalation claim is permanently bound to the depositor committed in its carry leaf and has no transfer path or parent-OI migration power. Liquidation cannot read, move, or acquire it. Final settlement pays the committed depositor after proof verification and replay protection. Example: Moving half a vault's capacity ownership leaves every escalation claim unchanged, and locking that claim cannot increase the OI routed to any child. Type Safety Enforcement status Reviewed preservation Primary evidence withdrawDeposit , liquidation design VAULT-03 Vault registry coherence Required property The vault registry is append-only and contains each nonzero address at most once. Registration does not require economic state: any vault path that calls _registerVault , including a public fee checkpoint for an empty address, can append it. Pagination is newest-registered first; later activity and full exit do not reorder or remove an entry. Consumers read current vault and escalation state to decide which registered vaults to display. Example: After a vault has fully exited and its REP backing, capacity ownership, claimable fees, escalation stake, bad debt, and open interest are all zero, its address remains in registry pagination. A UI can filter the empty position without losing historical discovery. Type Safety Enforcement status Reviewed preservation Primary evidence _registerVault , registry pagination and direct-claim boundary tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"forks","heading":"Forks and Child Isolation","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"FORK-01 Parent pool finality Required property Once the parent universe forks, parent operational flows freeze and the parent pool never re-enters Operational . Example: After fork activation, the parent cannot accept another REP deposit or later return from PoolForked to Operational . Type Safety Enforcement status Contract guard Primary evidence isOperational FORK-02 Immutable fork snapshot Required property When the parent first enters fork mode, it captures fork time, collateral, REP buckets, total REP backing units, escalation state, and capacity ownership exactly once. Those immutable values are the common basis for every child of that fork. Example: REP sent unsolicited to the parent after fork mode begins cannot change the REP snapshot used to initialize a later-created No child. Type Safety Enforcement status Reviewed preservation Primary evidence Fork snapshot preparation FORK-03 Isolated migration proxy Required property Each parent pool maps to exactly one deterministic migration-proxy address, and that address is the pool's isolated identity in the Zoltar migration ledger. Example: Two parent pools using the same forker derive different proxy addresses and therefore cannot spend each other's migration credit. Type Safety Enforcement status Reviewed preservation Primary evidence getMigrationProxyAddress FORK-04 Prefunded proxy isolation Required property Preexisting or unsolicited REP at the deterministic proxy cannot block initiation or become unclassified migration principal. Example: An attacker sending 1 REP to the future proxy address cannot make fork initiation revert or count that REP as pool migration principal. Type Liveness Enforcement status Reviewed preservation Primary evidence initiateSecurityPoolFork FORK-05 Eight-week migration boundary Required property Statoblast child-pool creation, pool-local vault migration, pool-proxy REP splitting, and own-fork claims are allowed through forkActivationTime + 8 weeks , inclusive, and are closed after that timestamp. forkActivationTime is recorded when the parent pool enters PoolForked ; post-migration activation begins only after its window ends. Share materialization has no expiry, but after the deadline it can target only an already-created child. This pool-local boundary does not limit Zoltar.deployChild , addRepToMigrationBalance , or splitMigrationRep ; those Zoltar operations follow their own fork identity and balance guards without this timestamp check. Example: Vault migration succeeds exactly at the eight-week deadline and reverts one second later. Type Safety Enforcement status Reviewed preservation Primary evidence forkActivationTime recording and child guards , MIGRATION_TIME , deployChild , addRepToMigrationBalance , and splitMigrationRep , migration deadline tests FORK-06 Child deployment identity Required property A deployed child must name the requesting pool as its parent and must use the child universe derived from that parent's selected branch. Its factory, forker, auction, share token, pool question, and security multiplier must equal the values supplied or inherited by the parent factory path. External-universe fork initiation requires the source pool to be authorized by its declared share token; own-game initiation does not. That relationship is not proof of configured-factory registration. The auction must have deployed code and must not already be trusted for another child. Example: On the external-universe path, the forker first requires the source to be authorized by its declared share token without treating that check as factory registration. The configured canonical factory installs the parent's share token, pool question, and multiplier in the child; the post-deployment guard then checks deployed and unique auction assignment plus parent, universe, factory, forker, and auction relationships before linking it. Type Safety Enforcement status Reviewed preservation Primary evidence _prepareForkState external-fork authorization guard , _validateChildPoolDeployment and deployChildSecurityPool FORK-07 Single-use non-escrowed vault migration Required property Vault migration converts the parent's backing-unit claim to REP, credits that REP amount as child-local backing units, and clears the corresponding parent REP backing units and capacity ownership before reuse. A positive parent claim that maps to zero child REP receives zero child backing units, so rounding cannot preserve a stale claim that later regains value. Claimable fees remain in the parent vault while proportional settlement collateral routes separately at pool level. Example: After Alice migrates her non-escrowed vault accounting to the Yes child, a second migration call cannot credit the same parent REP backing units to the No child. Type Conservation Enforcement status Reviewed preservation Primary evidence _migrateNonEscrowedVaultAccounting FORK-08 One-time aggregate continuation backing Required property Each selected child initializes the canonical carry snapshot and receives the complete aggregate continuation backing at most once. Optional vault cleanup only clears unresolved parent escalation-deposit accounting; it creates no child escrow and cannot change carried-proof eligibility. The continuation cannot resume until its game balance covers all effective unrelated-fork principal. For an own fork, initial backing must cover sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , the rounded-up 80% minimum, and the live balance must cover the unexported remainder after valid direct pre-resume claims. sourcePrincipalAtForkAttoRep is the raw aggregate stored by the fork snapshot before effective direct-claim deductions. Example: Creating the Yes child installs its snapshot and aggregate backing before Alice acts. Alice may later clear her unresolved parent escalation-deposit accounting, but that cleanup neither funds the child nor enables or disables her winning proof. Type Conservation Enforcement status Contract guard Primary evidence _ensureChildEscalationBacking , resumeFromFork , isForkCarryFundingComplete , _exportForkedEscrowByOutcome , and fork-backing regression tests FORK-09 Cumulative child collateral Required property Cumulative parent ETH transferred to children never exceeds the fork collateral snapshot and is derived from cumulative migrated REP, not per-call rounding. Example: If several calls collectively migrate R child REP, their collateral target is ⌈fork collateral × R / denominator⌉ , independent of how that same R is partitioned. The denominator is vaultRepAtForkAttoRep for an own fork and auctionableAttoRepAtFork otherwise. Type Conservation Enforcement status Contract guard Primary evidence _transferForkMigratedCollateralToChild FORK-10 Share migration integrity Required property Share migration preserves the holder's parent token-ID balance as a persistent entitlement, rejects duplicate or malformed target outcomes, and mints only the unmaterialized balance into each selected child. A used source balance cannot transfer. Example: Materializing seven parent Yes shares in one child leaves all seven parent entitlements visible and locked. A later call can mint seven into another existing child, but cannot mint the first child twice. Type Conservation Enforcement status Contract guard Primary evidence ShareToken.migrate FORK-11 Unequal child share supplies Required property Child pricing uses the fork-time economic claim supply rather than currently materialized outcome supplies. Unequal ERC-1155 supplies therefore do not block complete-set minting or proportional complete-set redemption. The denominator lifecycle and payout mechanics are owned by SHARE-04 . Example: If the economic supply is 12 while materialized supplies are 10, 8, and 6, a new complete set is priced against 12 and adds the same newly purchased balance to all three materialized supplies. Type Conservation Enforcement status Reviewed preservation Primary evidence setTotalSharesAttoShares during auction preparation , attoSharesToAttoEth , redeemCompleteSet , and redeemShares , mintCompleteSets and persistent migration accounting FORK-12 Child activation after settlement Required property Value-free truth-auction settlement activates a child with its tracked migrated collateral plus retained auction ETH, which cannot exceed the parent fork snapshot. Fork-time economic claim accounting keeps later minting and proportional burns independent from materialization order. Example: A child with 9 ETH against a 10 ETH fork snapshot activates after value-free finalization with 9 ETH of tracked collateral; its later operations remain subject to the ordinary share-supply and minting guards. Type Safety Enforcement status Reviewed preservation Primary evidence Truth-auction start and finalization","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"escalation","heading":"Escalation Games and Carried Claims","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"ESC-01 Total escrow reconciliation Required property totalDisputeStakedAttoRep equals effective live vault-denominated escrow plus effective aggregate continuation backing in forkCarryDisputeStakedAttoRep . Continuation backing is not assigned to child-vault health or migration power, but it must enter the aggregate before the truth auction so physically held REP cannot escape the repair haircut. Auction retention floors the aggregate total and the continuation bucket independently; do not reconstruct either aggregate by summing independently rounded vault views. Example: If vault-denominated escrow contributes 4 and 6 REP and an external-fork continuation carries 12 REP of aggregate proof backing, the total is 22 REP, represented as 22 × 10^18 attoREP in totalDisputeStakedAttoRep , before the auction. A 75% retention records ⌊22 × 75%⌋ = 16 total effective REP and ⌊12 × 75%⌋ = 9 aggregate continuation REP. This makes the carried claims absorb the same repair loss without pretending that their backing belongs to a particular child vault. Type Conservation Enforcement status Reviewed preservation Primary evidence Continuation initialization , truth-auction retention accounting ESC-02 Local unresolved REP reconciliation Required property totalLocalUnresolvedAttoRep equals the sum of unresolved local deposits, and each vault-local counter is its exact component. Example: If Alice has 3 unresolved REP and Bob has 5, their counters are 3 and 5 and the global local-unresolved total is 8. Type Conservation Enforcement status Reviewed preservation Primary evidence Unresolved counters ESC-03 Outcome carry reconciliation Required property For each outcome, currentCarryTotalAttoRep equals effective inherited unresolved principal plus unresolved local deposits. Effective inherited principal subtracts immediate-parent direct claims and becomes zero for an inherited losing outcome after finalization. Example: A child snapshots 7 Yes REP, the parent directly claims 2 REP, and the child receives a new unresolved 2 REP Yes deposit. Its current Yes carry is (7 - 2) + 2 = 7 REP . Type Conservation Enforcement status Reviewed preservation Primary evidence Escalation accounting invariants ESC-04 Stable deposit identifiers Required property An ordinary game's stable deposit identifier is its local deposit index. A continuation game derives the identifier from its own address, outcome, and local index, so two different continuation deposits cannot share a carry or nullifier key merely because their local indexes match. Example: Deposit zero on Yes in a child game and deposit zero on Yes in its grandchild produce different stable identifiers. Type Safety Enforcement status Reviewed preservation Primary evidence Stable deposit identity ESC-05 Single-use claims per branch Required property Along any single root-to-descendant fork path, each local or inherited deposit may be claimed, settled, refunded, or exported at most once. Child nullifiers are branch-local, but a direct ancestor claim invalidates the matching proof in every descendant and reduces their effective inherited principal. Otherwise, selected sibling branches maintain separate claim authorization. Example: Consuming a carried proof in a Yes grandchild prevents replay farther down that branch while a sibling remains independent. If the deposit was instead claimed directly from their ancestor, both descendants reject it. Type Safety Enforcement status Reviewed preservation Primary evidence Carry nullifiers , MMR proofs ESC-06 Aggregate export accounting Required property Aggregate unresolved export clears the exporting vault's three outcome totals, unresolved counter, and parent escrow exactly once. It retains the deposit rows and carry commitment solely as immutable proof material for selected children. Example: After Alice exports her aggregate claim, her parent escrow is zero even though her historical deposit row remains available to prove a child claim. Type Conservation Enforcement status Reviewed preservation Primary evidence exportVaultUnresolvedTotalsWithoutTransfer ESC-07 Authenticated carried winner payout Required property A carried claim must authenticate an unconsumed proof for the final winning outcome. The proof fixes the original claim identity and its committed depositor, who receives the normal winning payout even when another account relays the transaction. Liquidation cannot change that recipient. Example: Bob may relay Alice's valid winning proof, but the payout still goes entirely to Alice as the committed depositor. A losing proof cannot consume backing, and reward math may make a winning payout greater than its principal. Type Conservation Enforcement status Contract guard Primary evidence withdrawDeposit ESC-08 Game REP settlement conservation Required property Every REP unit held for a game is accounted for exactly once as winning principal, reward funding, losing principal, unsettled residual, ordinary-game REP swept back to the pool, or fork-continuation REP burned. Settlement cannot create an additional REP claim. Example: Paying winners and sweeping the final residual reduces the game balance to zero without total payouts exceeding the REP previously held. Type Conservation Enforcement status Reviewed preservation Primary evidence Settlement ESC-09 Terminal game outcomes Required property Once resolution or non-decision closes a game path, later deposits cannot reopen or change that terminal result. Example: After Yes becomes final, a later attempt to deposit on No reverts rather than changing the winner. Type Safety Enforcement status Contract guard Primary evidence EscalationGame ESC-10 Ancestor claim replay protection Required property If a deposit is claimed directly from an ancestor during an own fork, every existing or later descendant treats the corresponding proof as spent even if that descendant has not written its own nullifier. Example: Claiming deposit 4 from the parent blocks proof 4 in an already-created child and in a grandchild created later. Type Safety Enforcement status Reviewed preservation Primary evidence Forked claims ESC-11 Residual sweep preconditions Required property Residual REP cannot be settled until effective unresolved principal is zero for every outcome. For a continuation, the sweep first retires its inherited aggregate forkCarryDisputeStakedAttoRep ; after that retirement, no local dispute stake or vault escrow may remain in totalDisputeStakedAttoRep . Inherited losing principal retires at finalization without a proof; winning inherited proofs and every local unresolved deposit still require their applicable terminal transition. Ordinary-game residual returns to the pool, while fork-continuation residual is burned and cannot accrue to child-local backing units. Example: After Yes finalizes, an unclaimed inherited No leaf does not block sweeping. An unclaimed inherited Yes proof, an unsettled local deposit, or a live vault escrow record still does. Type Conservation Enforcement status Contract guard Primary evidence sweepResidualRepToSecurityPool ESC-12 Pool and continuation payout agreement Required property When a universe forks on a pool's question, each child pool and its continuation game use that child's Invalid, Yes, or No branch as the final pool-question outcome. Once a pool inherits that fixed outcome, new local escalation deposits and every later fork transition revert, whether the new universe fork uses the same question or another one. Deposit withdrawal reverts unless the pool and game report the same outcome. Example: A Yes child created by a fork on the pool question pays carried Yes deposits even if copied game balances favored No. It rejects new local dispute-staked REP before escrow, so a later local non-decision cannot lock vault redemption. A later matching or unrelated universe fork leaves the stored SystemState.Operational and fixed Yes outcome unchanged, and eligible share, vault REP, and carried-proof redemption paths remain available, while universe-fork guards still freeze normal operating calls. Type Safety Enforcement status Reviewed preservation Primary evidence activateForkMode , getQuestionResolution , fixed-outcome propagation , and pool/game payout check ESC-13 Non-decision threshold and live start-bond arithmetic Required property For a positive fork threshold F , non-decision requires ⌈F / 2⌉ REP on two outcomes. Therefore the two threshold balances total at least F , while one attoREP less on both outcomes totals strictly less than F . At pool construction, the configured start bond is max(1 REP, theoretical REP supply / 10,000,000) , and a new origin requires the live non-decision threshold to exceed it. If an existing pool's live threshold later falls to or below that configured bond, ordinary game deployment clamps the live start bond to nonDecisionThresholdAttoRep - 1 , provided the threshold exceeds one attoREP. Example: If F = 5 , non-decision requires 3 REP on two outcomes, so the two balances hold 6 REP; balances of 2 and 2 remain below the 5 REP fork threshold. Type Safety Enforcement status Contract guard Primary evidence getNonDecisionThresholdAttoRep , origin threshold admission , deployEscalationGame , and start-liveness regression tests ESC-14 Carry commitment structural integrity Required property For every outcome, exported carry peaks, leaf count, unresolved total, and nullifier root equal the corresponding outcome state. Only leaf-count-selected peak heights are occupied, independently bagging those peaks yields the exported root, and consumed proof indexes are unique. The accounting definition of unresolved total is owned by ESC-03 . Example: Three Yes leaves occupy the height-zero and height-one peaks; bagging those two peaks reproduces the exported Yes root before and after one inherited proof is consumed. Type Conservation Enforcement status Reviewed preservation Primary evidence getForkCarrySnapshot and getForkCarryRoots , bagCarryPeaks , independent carry-structure invariant tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"oracle","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . Accepted oracle design properties—not security findings. Statoblast uses profitable correction, not operation-value collateralization. Report liquidity is intentionally not required to equal or bound the value of withdrawals, liquidations, capacity ownership changes, pool assets, or cumulative operations that use an accepted price. The protocol also makes no bounded-liveness claim for a pending sponsor lane. Every valid dispute restarts settlement, incurs transaction execution, and must fund the contract-specified replacement position. Under ordinary non-dust parameters, protocol fees and the required position also accumulate; before the escalation halt, integer flooring can leave dust-sized rounds unchanged. A participant willing and able to keep submitting and funding disputes may therefore delay unrelated users without a coordinator-level deadline. This paid, capital-unbounded pause is intentional and is not a violation of a Zoltar liveness invariant. Security review should verify the correction-profit calculation, report accounting, dispute access, and the stated inclusion assumption. A concrete bypass of the required position or fee, capital reuse, invalid deadline transition, or cache-conformance failure remains a finding. The mere absence of a report-notional bound or absolute pending-report duration does not. ORA-01 Authorized settlement callbacks Required property Only the configured OpenOracle may supply a settlement callback, and its report identifier must equal the coordinator's pending report. Example: A callback from another contract or for an older report ID cannot update the cached REP/ETH price. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback ORA-02 REP-per-ETH price direction Required property An accepted price is positive REP per ETH: amount2 REP × 1e18 / amount1 WETH . Higher values require more REP for the same ETH obligation. Example: A report of 200 REP for 2 WETH stores 100 REP per ETH, not 0.01 ETH per REP. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback , ratio direction ORA-03 Five-minute price validity Required property A cached price is usable only while nonzero and strictly younger than the five-minute validity window. Example: A price settled exactly five minutes ago is stale; one settled four minutes and 59 seconds ago remains usable. Type Safety Enforcement status Contract guard Primary evidence isPriceValid ORA-04 Single-use staged operations Required property Every staged operation has one immutable identifier. Once execution is attempted with a usable price, success or a caught failure consumes that identifier, emits the result, and prevents a retry. An uncaught transaction revert instead rolls back the entire attempt. Example: A pool-level withdrawal rejection is caught and emitted as failed, after which the same operation ID cannot be executed again. Type Safety Enforcement status Contract guard Primary evidence executeStagedOperation ORA-05 Staged operation targets Required property Withdrawals target the initiating vault. Liquidations name separate operator, receiver, and target roles; the receiver must differ from the target, while the operator may equal either. A liquidation fails when the protected target REP-backing-unit or capacity-ownership snapshot becomes stale. Example: Alice can stage her own REP withdrawal. A liquidation operator can target Bob, but its receiver cannot be Bob; execution fails if Bob changes his REP backing units or capacity ownership after staging. Type Safety Enforcement status Contract guard Primary evidence Staging and snapshot checks ORA-06 Consumed execution failures Required property An expired operation, a liquidation with a stale target snapshot, a withdrawal that would move zero REP, or a liquidation inside the configured minimum price distance is consumed with an observable failure result rather than remaining executable forever. Example: If an earlier withdrawal empties a vault, its second queued withdrawal is consumed as a zero-effect failure rather than retried after every price update. Type Liveness Enforcement status Contract guard Primary evidence executeStagedOperation ORA-07 Staged-operation queue and index bijection Required property Operation IDs are append-only. Active enumeration contains each and only each operation with a live initiator; the bounded pending settlement queue contains unique active IDs; and pendingOperationSlotId is zero for an empty queue or equals its head. Example: Settling four queued operations removes those four IDs from both indexes, leaves an overflow operation active for manual execution, and does not reuse any consumed ID. Type Safety Enforcement status Reviewed preservation Primary evidence active and pending staged-operation indexes , queue/index invariant test ORA-08 Dispute and settlement boundary Required property The settlement deadline is exclusive: disputes require the current clock to be strictly before the deadline, while settlement is available at and after it. Example: At timestamp deadline , a new dispute is rejected and settlement is allowed. Type Safety Enforcement status Contract guard Primary evidence settle and dispute validation ORA-09 Oracle balance-domain boundary Required property Direct REP or WETH held by the coordinator is outside OpenOracle and remains untouched by settlement or recovery. OpenOracle credits balances by beneficiary rather than by report: the callback sends the coordinator's entire withdrawable internal REP and WETH credit to the recorded pending sponsor, including any third-party deposit that names the coordinator as beneficiary. Example: REP transferred directly to the coordinator remains there, while REP deposited into OpenOracle for the coordinator is swept with the report proceeds to the current sponsor and leaves only OpenOracle's balance sentinel. Type Conservation Enforcement status Reviewed preservation Primary evidence pendingReportSponsor and _withdrawOpenOracleReporterBalances , deposit and withdrawTo , coordinator/OpenOracle balance-domain test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"auctions","heading":"Truth Auctions","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"AUC-01 Bid admission Required property Bids are accepted only during the one-week window, at a supported tick whose computed ETH-per-REP price is positive, and at or above the auction's minimum bid size. Example: A bid submitted one second after the one-week window or below the minimum bid size reverts without being recorded. Type Safety Enforcement status Contract guard Primary evidence submitBid AUC-02 Auction caps Required property Final retained ETH never exceeds the ETH raise cap, and total purchased REP never exceeds the REP sale cap. Example: If bids offer 12 ETH against a 10 ETH cap, finalization retains at most 10 ETH and refunds the rest. Type Conservation Enforcement status Contract guard Primary evidence computeClearing and finalize AUC-03 Single-use bid settlement Required property Each bid is settled or refunded at most once; claimed state is set before an external ETH refund. The best-effort push is gas-bounded, and any rejected, reverted, or gas-exhausted positive refund is moved to bidder-specific pull escrow without reverting the bid's REP or capacity ownership settlement. Example: A bidder whose refund callback reenters, rejects ETH, or consumes its complete callback budget cannot withdraw the same bid a second time or starve the remaining settlement because the bid is already marked claimed and the push is gas-bounded; deferred ETH remains available through withdrawPendingEthRefund . Type Safety Enforcement status Contract guard Primary evidence withdrawBids and refund functions AUC-04 Claim-order independence Required property Cumulative allocation math makes per-bid REP, refund, capacity ownership, and bad-debt results independent of post-finalization claim order. Example: Alice claiming before Bob gives each vault the same REP, ETH refund, and capacity ownership as Bob claiming before Alice. Type Conservation Enforcement status Reviewed preservation Primary evidence allocateFromCumulativePosition AUC-05 Allocation and refund reconciliation Required property A zero-demand auction records zero purchased REP. Otherwise, the sum of all winning-bid REP allocations equals the stored aggregate purchase, and each bid's unretained ETH remains refundable. Example: If finalization stores 30 purchased REP, all winning claims together receive exactly 30 REP and every unretained attoETH is refunded. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction settlement AUC-06 Bidder-vault credits Required property A bid claim keeps purchased REP pool-held and transfers the bid's proportional REP backing units, capacity ownership, bad debt, and finalization-to-claim fees from the unassigned position to the bid's recorded vault. Each decrease in unassigned backing, capacity, and bad debt equals the corresponding vault credit. The auction rate is derived only from bounded child-local units, and cumulative credit cannot exceed the finalized denominator. These credits add to rather than replace that vault's migrated position, while capacity-ownership and bad-debt division dust follows each bid's fixed cumulative ETH position. This makes each winner's allocation independent of claim order. A winning dust bid still receives a positive fixed-position capacity ownership share when its REP allocation rounds to zero. Claiming does not change total capacity ownership, fee-eligible capacity ownership, active open interest, total bad debt, retention rate, or aggregate accrued fees. Example: A vault with migrated REP backing units keeps them when settling a winning auction bid. The claim moves the bid's backing units, deterministic capacity share, proportional bad debt, and accrued fees into that vault while leaving every pool aggregate unchanged. If a dust bid's REP share is zero but its capacity ownership share is positive, claiming that bid alone still credits the capacity ownership. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction-claim settlement AUC-07 Uniform weak-demand allocation Required property In underfunded clearing, qualifying ETH is retained bid ETH offered at or above the cap-implied qualification threshold. The threshold is not an execution-price floor. Qualifying bidders collectively receive maxAttoRepBeingSold in proportion to that ETH at the aggregate effective price underfundedWinningAttoEth / maxAttoRepBeingSold . Per-bid cumulative floors then assign indivisible attoETH and attoREP, so a dust winner can round to zero REP without changing the aggregate price. With no qualifying ETH, every bid refunds. Example: If Alice supplies 60% and Bob 40% of qualifying ETH, they receive 60% and 40% of the complete REP sale cap subject to deterministic attoREP rounding. Type Conservation Enforcement status Reviewed preservation Primary evidence Truth Auction clearing design AUC-08 Forced-ETH-resistant finalization Required property Auction finalization succeeds regardless of unsolicited ETH already present in the child pool or forker. Example: One attoETH forced into the child before finalization remains surplus and does not change protocol-accounted collateral. Type Liveness Enforcement status Reviewed preservation Primary evidence finalizeTruthAuctionRepair AUC-09 Bounded bid settlement Required property After the auction deadline, value-free finalization succeeds using migration-routed collateral plus retained auction ETH. Qualifying bids settle for REP, non-qualifying bids refund, and nonzero finalizer ETH reverts. Example: For a 10 ETH target with 6 ETH migrated and 3 ETH retained from bids, the child activates with 9 ETH of accounted collateral and every bid can then settle. Type Liveness Enforcement status Contract guard Primary evidence Weak-demand behavior , finalizeTruthAuctionRepair AUC-10 Persistent tick bid history Required property Bid cumulative ETH remains append-only for a tick even when the active AVL node is deleted and later recreated. Refunded history is subtracted exactly once, so every later accepted bid retains a valid allocation or refund path. Example: Deleting an empty price tick and later bidding at that price continues cumulative ETH after the old history instead of making the new bid unclaimable. Type Conservation Enforcement status Reviewed preservation Primary evidence _appendBid and refund-prefix accounting and repeated tick deletion/recreation tests AUC-11 Auction tree and public-model equivalence Required property Before finalization, active tick pages contain exactly the nonzero ticks not removed by pre-finalization refunds, in descending order, and clearing computed from the AVL tree equals an independent calculation over those bids. Finalization freezes that tree and clearing result: later claims change bid flags and ETH liabilities but not active tick pages. Post-finalization liabilities are owned by AUC-12 . Historical insertion and refund-prefix obligations are owned by AUC-10 . Example: Before finalization, an independent descending-tick scan returns the same clearing result as the contract. After a winner claims, its bid is marked claimed and its refund liability leaves, while the final clearing page and result remain unchanged. Type Safety Enforcement status Reviewed preservation Primary evidence AVL tree and clearing , enumeration and auction integration , seeded independent-model and frozen-snapshot tests AUC-12 Auction ETH liability conservation Required property Before finalization, auction ETH equals active unrefunded bids plus aggregate pendingEthRefundsAttoEth and explicit surplus. After finalization, the remaining balance equals refunds still attached to unclaimed bids plus deferred pendingEthRefundsAttoEth and explicit surplus. Per-bid settlement partitions and aggregate REP allocation are owned by AUC-05 . Example: If a rejected pre-finalization push removes a 2 ETH losing bid from active clearing, raw ETH still equals the remaining active bids plus the 2 ETH deferred refund. After finalization, the same deferred bucket composes with unclaimed-bid refunds and any forced surplus. Type Conservation Enforcement status Reviewed preservation Primary evidence finalize , withdrawBids , and deferred refunds , seeded and deferred-refund ETH-liability tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"lifecycle","heading":"Lifecycle, Liveness, Observability, and External Calls","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A02 viable operating costs , A16 timely inclusion , A06 lifecycle executors , and A26 Ethereum execution . LIFE-01 Pool state transitions Required property Pool state topology permits only Operational → PoolForked for a parent and ForkMigration → ForkTruthAuction → Operational for a child. Example: A child cannot skip directly from ForkMigration to Operational while repair auction work remains required. Type Safety Enforcement status Reviewed preservation Primary evidence SystemState , fork transitions ; see FORK-12 for child accounting consistency. LIFE-02 Permissionless transition liveness Required property Once a resolution, migration, auction-completion, or withdrawal function's documented semantic preconditions hold, dust, unsolicited balances, empty collections, or earlier permissionless calls cannot permanently prevent some caller from completing it. Example: Forced ETH and an empty losing-bid list do not prevent a caller from finalizing an otherwise complete truth auction. Type Liveness Enforcement status Proposed guard Primary evidence FORK-04 , BAL-02 , and AUC-08 LIFE-03 Atomic failure behavior Required property An uncaught revert rolls back every state mutation and transfer in the transition; deliberate consumed failures emit an explicit result and cannot be retried. Example: If a child migration reverts after an attempted token transfer, neither the transfer nor its accounting update remains committed. Type Safety Enforcement status Reviewed preservation Primary evidence Solidity atomicity, coordinator consumed-failure paths LIFE-04 Deadline equality rules Required property Every deadline assigns equality to one documented side of the boundary: migration remains open at equality; auction bidding and public pool finalization are both closed at equality, and finalization opens immediately afterward; oracle disputes are closed and settlement is open at equality; staged operations expire only after equality; and an escalation result is final only after its end timestamp. Example: Exactly at an auction deadline, both a bid and public pool finalization revert; one second later, finalization succeeds. Type Safety Enforcement status Proposed guard Primary evidence Boundary tests across fork migration, auctions, escalation, and OpenOracle LIFE-05 Bounded transition work Required property No mandatory transition loops over an attacker-unbounded history in one transaction; callers use bounded pages, fixed outcome sets, proofs, or balanced-tree traversal. Example: Settling one carried deposit verifies its bounded proof instead of iterating over every deposit ever made in its ancestors. Type Liveness Enforcement status Reviewed preservation Primary evidence Vault pages, bid pages, MMR proofs, and auction AVL traversal OBS-01 Event-state replay equivalence Required property Ordered canonical events reconstruct the same universe, pool, vault, coordinator, and escalation carry state exposed by storage. Replay isolates contract-local counters and recognized emitters, and every replay-supported state transition emits the checkpoint or delta needed to reach its resulting storage value. Example: Replaying seeded pool and vault operations matches their accounting storage, while replaying a deployed parent deposit, fork snapshot, and child carry checkpoint matches the child's roots, peaks, leaf counts, unresolved totals, and nullifier roots. Type Safety Enforcement status Reviewed preservation Primary evidence contract interaction reference , event replay model , actual-log storage-equivalence tests EXT-01 Genesis token behavior boundary Required property Genesis REP and configured WETH obey the exact transfer, return-value, decimal, and callback behavior assumed by pool and oracle accounting. Mainnet relies on external token behavior; Sepolia relies on verified deployments of the reviewed GenesisReputationToken and WETH9 contracts for issuance and transfer mechanics. Burn-sink inaccessibility remains an external assumption on both networks. Example: Genesis REP transfer returns and moves exactly the requested attoREP rather than charging a fee that leaves pool accounting overstated; child REP supply remains bounded by its contract-enforced theoretical ceiling. Type External assumption (mainnet); mixed safety and external assumption (Sepolia) Enforcement status External on mainnet; reviewed issuance and transfer guards under A23 and A26 on Sepolia, with burn-sink inaccessibility still assumed Primary evidence SafeERC20Ops , GenesisReputationToken , WETH9 , the network deployment manifests, and security assumption A21 Related enforced property Canonical child REP behavior is enforced by ReputationToken and its Zoltar-only mint and burn authority. EXT-02 Callback-safe accounting Required property External ETH or token callbacks observe effects already committed for that action. A required delivery that fails reverts without leaving partial accounting; an explicitly deferrable delivery instead commits the action together with an exactly accounted recipient liability. Example: A fee recipient's fallback observes its claimable fees already cleared, so reentry cannot redeem the same claimable fees twice. Type Safety Enforcement status Reviewed preservation Primary evidence Pool redemption, auction refund and deferred-credit, and oracle refund paths Related external liveness boundary A22 participant-controlled recipient compatibility EXT-03 ERC-1155 callback safety Required property ERC-1155 receiver callbacks cannot mint beyond pool capacity, alter the amount minted, or reenter the same accounting effect twice. Example: A receiver callback during complete-set minting cannot call back into the pool and reuse the same ETH capacity for a second mint. Type Safety Enforcement status Reviewed preservation Primary evidence createCompleteSet , ERC-1155 receiver checks, and receiver callback tests Related external liveness boundary A22 participant-controlled recipient compatibility EXT-04 Same-block deadline ordering Required property At a deadline timestamp, mutually exclusive before-deadline and after-deadline actions cannot both succeed. Explicit comparison operators assign equality to one phase, and transaction ordering within the block cannot change that assignment. Example: At the OpenOracle settlement deadline, ordering a dispute before settlement in the block still cannot make the dispute valid. Type Safety Enforcement status Proposed guard Primary evidence ORA-08 and same-block auction ordering analysis EXT-05 Recursive fork gas bound Required property Origin registration, inherited-fork detection during child construction, and direct-claim replay checks must not traverse universe or pool ancestry. Example: At lineage depths 1 and 32, a replay query for the same global deposit id performs the same lineage-registry and claimed-id lookups rather than 1 or 32 parent calls. Type Liveness Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory.getOriginId and getPoolId , cached inherited-fork registry tests, SecurityPoolForker.getEscalationDepositId , and depth-independent replay gas tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["assumptions","security","threats","guarantees"],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Statoblast and Zoltar enforce accounting, authorization, and lifecycle rules onchain. Their economic safety and practical liveness still depend on the participant, market, asset, deployment, client, data, Ethereum, and cryptographic assumptions below. An assumption is not a guarantee enforced by the contracts. Each entry says what must remain true, what can fail if it does not, and where the related mechanics are explained. The Protocol Invariants catalog lists contract-enforced properties and their implementation evidence. Participation Forks and markets Oracle and liveness Technical foundation Excluded guarantees","title":"Security model","topic":"Safety","weight":1},{"fragment":"orientation","heading":"Security-model orientation","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"This catalogue protects REP, ETH settlement collateral, outcome shares, vault backing, escalation deposits, child-universe accounting, and the integrity of event-derived state. Boundary Examples Trusted protocol components Zoltar, security pools, share tokens, coordinators, forkers, and their configured delegates. External dependencies OpenOracle, WETH/REP token behavior, Ethereum execution and finality, RPCs, wallets, and proof data. Untrusted actors Traders, vault owners, liquidators, disputers, bidders, settlers, keepers, and indexers. Failure outcomes Reverted calls, delayed transitions, underfunded child repair, stale prices, incorrect external data, or unreconstructable history. The assumptions below explain what must remain true for these assets and transitions to retain their intended safety or liveness properties; they are not additional contract guarantees.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-value","heading":"Participation and coordination","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions explain why someone performs the economically useful action instead of merely being permitted to perform it.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a01","heading":"Participants act on risk-adjusted economic incentives","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Enough participants prefer actions with greater expected value after gas, fees, delay, capital lockup, and risk. The model does not rely on altruistic defense. Failure boundary: A nominally profitable liquidation, dispute, bid, migration, or fork defense may not happen when its risk-adjusted return is unattractive. Mechanics: economic roles , liquidation incentives , auction clearing , and oracle incentives .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a02","heading":"Required actions remain worth their operating costs","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each time-sensitive action, transaction fees, opportunity cost, coordination cost, and capital lockup remain small enough relative to the value protected or earned. Failure boundary: An action can be technically executable but economically abandoned before its deadline. Mechanics: auction gas bounds , oracle gas parameters , and escalation deployment costs .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a03","heading":"Each economic role has enough independent participants","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Trading, liquidation, escalation, migration, auction bidding, price correction, and settlement have enough non-colluding participants for the relevant action. Secondary-market trading is external to Statoblast. Failure boundary: Thin or controlled participation can remove price discovery, competitive bidding, or an opposing outcome deposit. Mechanics: economic roles , fork coordination , and auction participation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a04","heading":"Participants can mobilize REP to raise an alarm","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Market participants can mobilize enough vault-backed REP through vault owners to fund an early competing Invalid , Yes , or No deposit and keep the local contest active while additional capital reacts. Failure boundary: A wrong local outcome can become final before better-capitalized participants enter. Mechanics: escalation resolution , escalation accounting , and operator guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a05","heading":"Participants value continued access to the supported Statoblast lineage","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The participants whose coordination determines durable use value continued access to the supported Statoblast lineage at least as highly as its configured statoblastSecurityMultiplierBps basis points of the value secured by that lineage. BPS_DENOMINATOR = 10_000 converts that stored value to its human multiplier. Value Statoblast access × BPS_DENOMINATOR ≥ statoblastSecurityMultiplierBps × Value secured Compare one lineage’s continued-use value and secured value at the same pre-attack time. Failure boundary: Participants may rationally abandon, grief, or coordinate away from a lineage whose continued-use value is too small. Mechanics: fork security , fork migration , and Statoblast security boundary .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a06","heading":"At least one executor advances every required public transition","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"An adequately funded participant or service monitors and calls each needed permissionless transition, including liquidation initiation, local escalation deposits and settlement, fork activation, child creation, vault and share migration, auction start and finalization, bid settlement, OpenOracle report settlement, settled-report callback recovery, overflow operation execution, and proof-based claims. Failure boundary: Permissionless state can remain stalled even though no contract authorization prevents progress. Mechanics: lifecycle liveness , fork operations , and oracle operations .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-forks","heading":"Forks, truth, value, and liquidity","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions connect onchain branching and accounting to offchain truth judgments, asset value, liquidity, and migration.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a07","heading":"A supported lineage preserves enough aggregate child value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each protected Statoblast origin lineage, evaluate the parent and its child-pool continuations in one numeraire at the fork-security decision horizon, before long-run value has fully concentrated on one branch. The sum of traded child-lineage values remains large enough compared with that parent lineage’s pre-fork value under the origin’s immutable statoblastSecurityMultiplierBps . Each child asset is valued in its own branch; unrelated external collateral is not counted again. Value parent lineage ≤ ∑ child ∈ Lineage continuations Value child × BPS_DENOMINATOR statoblastSecurityMultiplierBps lineage Parent and child values use the same origin lineage, valuation source, numeraire, and decision horizon. For multiplier 20_000 BPS , aggregate child-lineage value must be at least twice parent-lineage value; multiplier 30_000 BPS requires three times that value. Failure boundary: Value destruction or fragmentation can make a malicious or unnecessary fork cheaper than the harm it causes. Mechanics: fork economics , fork coordination , and pool migration .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a08","heading":"REP market capitalization approximates economic value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At the stated valuation time and system scope, REP market capitalization is a sufficiently accurate observable proxy for the discounted cash flow participants expect from REP. Failure boundary: Thin, manipulated, or speculative pricing can overstate the economic value securing open interest. Mechanics: REP economics and economic backing claims .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a09","heading":"Users value the universes they judge truthful","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users prefer the universe or universes they judge truthful, so economic activity and durable asset value concentrate there. Unlike A07’s fork-security decision horizon, this assumption describes the later, durable allocation of value after users coordinate. Value before fork ≈ Value truthful universe When several branches remain plausibly truthful, document the value split instead of assuming one winner. Failure boundary: Coordination on a false branch, or durable fragmentation across branches, defeats the value-concentration security argument. Mechanics: Zoltar security and child outcome resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a10","heading":"Participants can access timely outcome evidence","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users can obtain enough reliable real-world evidence before the relevant protocol deadline to judge which universe is truthfull. Failure boundary: Missing, delayed, or conflicting evidence can split honest capital or let a false local outcome become final. Mechanics: question encoding , invalid outcomes , and child resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a11","heading":"Migration is operationally practical","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users, exchanges, interfaces, custodians, and other integrations are willing and able to migrate assets and state into the child universe or universes they support during the applicable windows. Failure boundary: Supported branches can remain illiquid or undercollateralized because economic claims and backing do not move there. Mechanics: REP splitting , pool migration , and operator migration guidance .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a12","heading":"Truth auctions can convert supported child REP efficiently","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"When repair is needed, enough demand can buy some supported-child REP for ETH at an acceptably small discount and operating cost. This does not assume that the full repair target is raised. Failure boundary: Weak or strategically withheld demand leaves the child operational with impaired collateral. Mechanics: auction lifecycle , clearing , and repair accounting .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a13","heading":"REP is sufficiently liquid","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can acquire or sell the required REP quantities without price impact or delay large enough to invalidate reporting, disputing, liquidation, auction, or migration economics. Failure boundary: Capital may exist in principle but cannot be assembled at a usable price before the deadline. Mechanics: economic roles , escalation , and oracle positions .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a14","heading":"REP economic value exceeds the open interest it secures","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For a stated pool, lineage, or system scope at one timestamp, open interest and REP discounted cash flow are valued in the same numeraire, and REP discounted cash flow remains strictly greater. For a pool-level evaluation, open interest means the pool’s protocol-accounted ETH collateral unless the analysis explicitly names a broader exposure measure. The REP supply and price basis must also be stated; market capitalization is the observable proxy in A08 . This external relationship is separate from each pool’s onchain multiplier-adjusted REP backing check. Open Interest < Discounted Cash Flow REP Record scope, timestamp, common numeraire, price source, REP supply basis, and open-interest definition when evaluating this strict inequality. Failure boundary: REP holders may rationally accept or cause losses larger than the durable REP value placed at risk. Mechanics: security pools , liquidation backing , and oracle exposure analysis .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a15","heading":"Question selection remains aligned with user intent","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces and users verify the immutable question ID and metadata when selecting a market or pool, and the question has evidence and wording from which participants can reach an outcome or intentionally choose Invalid . Failure boundary: Unverified selection can route users and capital to an unintended market or pool, while ambiguous wording or unavailable evidence can split local outcome coordination. Mechanics: UNI-02 , question identity , and pool selection .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-oracle","heading":"Oracle correction, inclusion, and operational data","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions cover truthful REP/ETH price discovery, deadline access, capital, independent correction, and the offchain information required to act.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a16","heading":"Deadline transactions can obtain Ethereum inclusion","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Honest users can submit competitively priced Ethereum transactions and obtain canonical inclusion within every relevant dispute, escalation, migration, auction, staging, and settlement window. Failure boundary: Censorship or congestion lasting through a deadline can finalize a wrong report or prevent a supported action. Mechanics: deadline invariants , auction windows , and oracle censorship model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a17","heading":"OpenOracle has an available, adequately capitalized corrector","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one OpenOracle participant continuously monitors pending REP/ETH reports, can identify candidate errors using the reference market in A19 , can fund the scenario-dependent WETH and REP replacement position plus transaction costs, and can obtain inclusion before settlement. The participant's net incentive to perform the correction is the separate boundary in A18 . Failure boundary: An incorrect report can settle and authorize price-sensitive pool actions when no capable participant detects, funds, and includes a correction. Mechanics: REP/ETH oracle , correction incentives , and attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a18","heading":"At least one available corrector has an independent net incentive","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one participant satisfying A17 is not under common control with the sponsor-funded coordinator reporting path, does not share the manipulation payoff, and is not bribed or compensated. The participant's net payoff from correcting is strictly greater than its net payoff from leaving the harmful report uncorrected. Two external submissions are not required for every request. Failure boundary: A sponsor-controlled, bribed, or economically indifferent corrector can make the contestable report path behave like a single trusted reporter. Mechanics: settlement validation scope , economic tradeoffs , and oracle invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a19","heading":"A robust reference market makes harmful REP/ETH errors correctable","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Independent participants can observe a manipulation-resistant reference REP/ETH price. Every price error large enough to unlock a security-relevant pool action also leaves a correction opportunity that remains profitable after actual gas, prevailing priority fees, OpenOracle fees, price impact, and required capital lockup. Failure boundary: A harmful deviation below the profitable correction threshold, or a manipulated/illiquid reference market, can settle without an economically rational challenge. Mechanics: report sizing , external-payoff model , and settlement limits .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a20","heading":"Canonical chain data and proof construction remain available","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can access reorg-aware Ethereum history, recognized contract addresses, OpenOracle report state, and the event data needed to construct carry and nullifier proofs before use. Failure boundary: A valid dispute, settlement, migration, or inherited claim can become practically unavailable even while its contract entrypoint remains live. Mechanics: event replay , carry proofs , and operator orientation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-technical","heading":"Assets, clients, deployment, Ethereum, and cryptography","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions define the technical foundation below the protocol’s onchain guards.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a21","heading":"Genesis REP and configured WETH obey the required accounting model","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Genesis REP and configured WETH move exactly the requested base units, use 18 decimals, return supported ERC-20 values, and do not rebase, charge transfer fees, invoke unexpected callbacks, blacklist protocol contracts, pause required transfers, or permit unauthorized minting. WETH remains redeemable 1:1 for ETH, the genesis theoretical supply is accurate, and the configured genesis REP burn sink remains inaccessible. On mainnet these token behaviors are external assumptions. On Sepolia, the reviewed GenesisReputationToken and WETH9 enforce issuance and transfer behavior, provided A23 verifies their deployed bytecode and wiring and A26 preserves Ethereum execution. The configured burn sink's inaccessibility remains an external assumption on Sepolia as well as mainnet. Canonical child REP is likewise implemented by the reviewed ReputationToken contract rather than being an additional external-token assumption. Failure boundary: Origin-pool, genesis-fork, or oracle accounting can overstate balances, backing, burned supply, or WETH value. Mechanics: EXT-01 , genesis REP handling , and oracle token pair .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a22","heading":"Participant-controlled recipients can accept required asset deliveries","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Vaults, bidders, oracle sponsors, traders, share holders, and other caller-controlled recipients use addresses that accept native ETH when claiming fees, collateral, proceeds, or refunds. Contract recipients also return the required ERC-1155 receiver selector when complete-set creation, share migration, or an outcome-share transfer delivers tokens to them. A rejected ETH push may revert or become bidder-specific deferred credit; a rejected ERC-1155 callback reverts the mint or transfer and its surrounding protocol action. No protocol path can force a rejecting address to accept a later delivery or redirect every payout or mint. Failure boundary: A contract account that rejects ETH or the required ERC-1155 callback can strand its own payout or make its complete-set, migration, transfer, or claim action unavailable even while protocol-wide accounting remains safe. Mechanics: ETH callback safety , ERC-1155 callback safety , share-token receipt , auction refunds , oracle request refunds , and pool payout guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a23","heading":"Users select verified canonical deployments","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The reviewed release is built reproducibly with its pinned compiler and toolchain configuration. Release artifacts identify the manifest addresses and reviewed bytecode hashes. Users and integrations compare locally rebuilt and deployed runtime bytecode with those reviewed hashes, then verify constructor-installed dependencies, factory and forker provenance, pool lineage, question ID, and manifest. Constructor admission is not a safety proof. Failure boundary: Toolchain drift, unavailable or mismatched reviewed hashes, permissionless lookalike instances, wrong dependencies, or unintended lineage and question wiring can bypass the reviewed protocol even when the selected bytecode functions as configured. Mechanics: release posture , deployment status , compiler configuration , OpenOracle build provenance , deployment bytecode , and authority invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a24","heading":"Clients, RPCs, and wallets preserve user intent and canonical state","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces, indexers, RPC providers, and wallet software return canonical, reorg-aware state; construct and display the intended chain, target, calldata, parameters, and ETH value; and submit only transactions the user authorizes. Users verify transaction previews rather than treating an application label as an onchain guarantee. Failure boundary: Compromised or stale offchain software can misroute signatures or capital, hide deadlines or events, or cause users to authorize valid but unintended calls without violating contract guards. Mechanics: deployment discovery , contract interactions , and operator verification .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a25","heading":"Immutable parameters preserve economic and executable security margins","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The selected parameter relationships keep the fork threshold and haircut economically meaningful, the initial escalation deposit affordable, the pool multiplier and effective pool-held vault REP backing multiplier large enough for their distinct coverage roles, the effective pool-held vault REP backing multiplier at least the 10,500-BPS liquidation-award reserve, the oracle target error profitable to correct after fees and gas, dispute and settlement windows usable, and callback and transition gas executable. The Statoblast pool statoblastSecurityMultiplierBps is distinct from openOracleSecurityMultiplierBps . Failure boundary: Weak or internally inconsistent immutable parameters can defeat the economic or liveness argument even when the canonical contracts function exactly as configured. Mechanics: Statoblast parameters , fork economics , liquidation economics , oracle parameters , and oracle attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a26","heading":"Ethereum preserves the execution environment the protocol relies on","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Ethereum continues to provide correct EVM execution and atomic rollback, sufficient canonical finality, usable timestamp and block.basefee semantics, and block gas limits and gas schedules under which every mandatory immutable transition remains executable. Failure boundary: Deep reorganizations, invalid state execution, adversarial time behavior, or gas repricing can reverse accepted state or strand required transitions. Mechanics: reorg handling , atomicity and deadlines , and bytecode and gas bounds .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a27","heading":"Ethereum cryptographic primitives remain secure","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Keccak-256 remains collision- and preimage-resistant at the truncated widths used by the protocol, and CREATE2 identity remains sound. secp256k1 ECDSA signatures remain unforgeable with correct EOA signer recovery for Ethereum transactions and EIP-712 authorizations. OpenOracle's fixed Permit2 singleton additionally validates either an EOA signature or an ERC-1271 contract-account signature correctly. OpenOracle fixes the Permit2 address, requires the exact permitted amount, sends the requested tokens to itself, and constructs the witness from the beneficiary, relayer, token owner, and intent. Permit2 defines the EIP-712 domain, deadline, permitted-amount ceiling, unordered-nonce replay protection, and EOA/ERC-1271 signature validation. Verification under A23 must cover the pinned Permit2 implementation at that fixed address. Failure boundary: Forged identities, colliding questions or universes, false carry proofs, forged EOA transactions, incorrect EOA recovery or ERC-1271 acceptance, or broken domain and nonce enforcement can violate otherwise-correct guards or authorize an unintended token transfer. Mechanics: universe identity , proof hashing , Permit2 deposit authorization , Permit2 signature interface , pinned Permit2 implementation provenance , and deterministic deployments .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a28","heading":"Participants retain control of account authority","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants protect the private keys, signing devices, session permissions, and delegated ERC-20 or ERC-1155 approvals that authorize their accounts. Account recovery and custody arrangements do not give an unintended party effective control during a security-sensitive window. Failure boundary: A stolen key, compromised signer, or overbroad approval can authorize valid but unwanted transfers, deposits, bids, migrations, or configuration changes without violating a contract guard. Mechanics: transaction authority , vault and pool operations , and account-authority monitoring .","title":"Security model","topic":"Safety","weight":0},{"fragment":"excluded-guarantees","heading":"Guarantees deliberately not made","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These outcomes remain possible without contradicting the security model. They should be included in user, operator, and integration risk disclosures. EG01. Zoltar creates valid branches but does not enforce that the fork question is relevant to a user, establish the objectively truthful branch, obtain user consent to a fork, or force users to coordinate on one branch. EG02. A truth auction may raise less than its repair target; the child then activates with migrated collateral plus accepted auction ETH. EG03. OpenOracle report liquidity does not cap the notional value of operations using an accepted price, and settlement does not prove external price correctness. EG04. A participant with unbounded capital can keep funding valid disputes and delay the sponsor lane indefinitely; no bounded coordinator-availability claim is made. EG05. The immutable protocol has no administrator who can pause, upgrade, roll back, or rescue an unsafe deployment after launch. EG06. Escalation principal and expected rewards are not senior to open-interest repair. A child truth auction may proportionally reduce them, and a losing escalation claim pays its committed depositor nothing.","title":"Security model","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["operators","guardrails","launch","recovery"],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Operators, indexers, reviewers, and UI maintainers consult this reference for contract-accurate guardrails, launch procedures, and edge-case behavior in one subsystem at a time. The explanations carry the full protocol story. Each section answers a different operational question: how the immutable launch posture constrains releases, what a security pool or escalation game will and will not accept, how fork migration behaves, how auction settlement works, and how the REP/ETH coordinator stages or recovers operations. Implementation guardrails map to their contract sources so operators can check each rule against the exact Solidity implementation. The Protocol Invariants catalog connects those local guardrails to cross-contract conservation, lifecycle, liveness, and economic security properties, including launch-critical properties that the current contracts do not preserve. The Protocol Security Model is normative for the protocol's external assumptions.","title":"Operator guardrails","topic":"Operations","weight":1},{"fragment":"security-review-orientation","heading":"Security Review Orientation","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before classifying an economic or liveness concern, use the security model's grouped assumptions to identify external dependencies, then follow its canonical mechanics links. The invariant catalog owns the current requirement, status, and evidence for EXT-05 recursive-fork gas behavior . Operator work relies particularly on A23 verified deployments , A06 lifecycle executors , A20 chain-data and proof availability , A26 Ethereum execution and finality , A27 cryptographic security , A15 intended question selection , A25 safe immutable parameters , A22 asset-recipient compatibility , A24 client, RPC, and wallet integrity , and A28 account authority .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"assumption-monitoring","heading":"Assumption Monitoring","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The security model is the canonical definition of each boundary. This table assigns operational evidence and response for assumptions whose truth can change with market or service conditions. Evaluate each signal at the same lineage, pool, timestamp, numeraire, and decision horizon as the protected action. Assumptions Operational evidence Review cadence Response when the boundary is weak or unknown A01, A02, A03, A06 Expected reward after gas, fees, delay, and capital lockup; number and independence of active liquidators, escalation participants, bidders, correctors, and transition executors; settle-ready OpenOracle reports, callback-recovery candidates, and overflow operations Continuously for deadline-bearing flows; before launch and after parameter or gas-regime changes Fund or arrange independent executors, reduce exposed value, or disclose that the affected transition may stall A05, A07, A09, A11 Parent and supported-child lineage value in one numeraire; observed user, custodian, exchange, interface, and proof-tool readiness to migrate assets and state within the applicable windows; documented continued-use value and secured value Before a fork-security claim and throughout migration and coordination windows Do not claim fork deterrence, practical migration, or value concentration; narrow supported lineage and exposure A08, A13, A14 REP supply basis, price source, depth and price impact; REP discounted-cash-flow methodology; protocol-accounted ETH collateral or explicitly broader open interest At launch, continuously while open interest exists, and after material price, supply, liquidity, or exposure changes Cap or stop new exposure in clients and disclosures; do not describe REP backing as economically sufficient A04, A10 Outcome evidence sources and availability; immediately mobilizable REP and transaction budget before each local-escalation deadline From question creation through local resolution and any fork window Mark the market or action impaired, surface the deadline, and avoid asserting that honest local correction remains fundable A12 Auction demand, indicative REP/ETH depth, expected discount, and bidder operating cost Before and during every repair auction Disclose likely under-repair and plan operation of the child at only migrated collateral plus accepted auction ETH A16, A26 Inclusion delay, base fee, priority fee, reorg depth, block gas limit, and gas schedule against each immutable window and mandatory transition Continuously while a deadline or mandatory transition is live Raise transaction fees where rational, use independent submission paths, and disclose missed-deadline or stranded-transition risk A17, A18, A19 Independent corrector availability, ownership and payoff independence, scenario-dependent WETH and REP replacement capital, reference-market depth, manipulation cost, correction profit after fees/gas/impact, transaction budget, and settlement inclusion Continuously while a report is pending or a cached price can authorize operations Block or discourage new price-sensitive operations and disclose that an incorrect report may settle A21, A22 Mainnet external-token code and behavior; Sepolia reviewed Genesis REP/WETH runtime bytecode, constructor allocations, and wiring; WETH redeemability; burn-sink status; recipient ETH/ERC-1155 compatibility At deployment verification and before first use of a new recipient or integration Reject the deployment or incompatible recipient; do not rely on redirected recovery A20, A23, A24, A27, A28 Pinned compiler and toolchain identity; reproducible-build output, reviewed bytecode hashes, release manifest, deployed runtime code and wiring; independent reorg-aware RPC/event replay; hash and CREATE2 verification; signer, typed-data domain, nonce, witness, session, and approval inventory At release, on every environment or account change, and continuously for indexers Stop signing or operating, reject an unverifiable deployment, revoke compromised authority where possible, and re-establish canonical state from independent sources A15 Immutable question ID and metadata, evidence plan, and pool relevance Before pool selection Reject or warn on the selection; do not represent an admitted question as relevant or resolvable A25 Deployed immutable values and every documented economic, timing, integer-width, and gas relationship Before deployment and after any change in external gas or market conditions used by the model Treat the immutable deployment as unsupported; there is no administrative parameter repair","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"immutable-protocol-release-posture","heading":"Immutable Protocol Release Posture","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Zoltar and Statoblast are intended to launch as immutable, permissionless contracts. Launch documentation should not assume an operator can pause, upgrade, roll back, or disable protocol behavior after deployment. Release work should instead publish verifiable provenance: final commit and tag, deterministic addresses, CI and local QA results, production UI artifact hash, and any known limitations. UI deployments may describe data freshness and route users to verified artifacts, but they must not be documented as emergency controls over the protocol. This is the protocol's EG05 no-pause, upgrade, rollback, or rescue boundary .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"launch-release-checklist","heading":"Launch Release Checklist","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before tagging a launch release, run bun run ui:build:prod from a fresh dependency install and run bun run check:mainnet-deployment . This command fails when either generated deployment manifest is stale. Refresh and review both manifests with bun ./scripts/check-mainnet-deployment.mts --write before rerunning the check. That command writes and checks both mainnet-deployment-addresses.json and sepolia-deployment-addresses.json . Use the pinned compiler configuration in solidity/ts/compile.ts , the exact tool versions in solidity/package.json , and the imported OpenOracle profile in UPSTREAM.md . Publish the reproducible-build runtime hashes with the release, keyed to the manifest addresses, and compare both a clean local rebuild and deployed runtime code with those reviewed hashes before marking the release canonical. Push the final v* tag only after the CI Gate succeeds on the same commit. The Build and Push to IPFS workflow must succeed for that tag, and the resulting GitHub release must record the IPFS hash emitted from the published artifact. Production UI release notes should state that ?simulate=1 is a browser-local sandbox. Mainnet and Sepolia quote-dependent actions use live RPC data plus available network-local Uniswap liquidity, while simulation uses its configured mock where supported. The OpenOracle integration guide owns the quote-source order and network-address details. RPC and client integrity remain covered by A24 , while quote availability and representativeness are official-client limitations rather than protocol security assumptions. Stale, unavailable, or unsupported quotes are production blockers for the affected client action, not inputs that should be replaced with simulation prices.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"genesis-rep-deployment","heading":"Genesis REP Deployment","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Sepolia genesis REP allocations are canonical source data in sepoliaRepAllocations.ts . The configured 11 million REP mint cap is divided equally among the listed holders; adding a holder automatically reduces every holder's allocation. Integer division may leave the total minted supply below the cap by less than one attoREP per holder. Review both the holder list and the computed bigint total before deployment. The GenesisReputationToken constructor requires at least one allocation, matching holder and balance array lengths, unique nonzero holders, nonzero balances, and a total supply no greater than 11 million REP. It emits the standard ERC-20 mint Transfer event once for each allocation. The sum of the constructor balances is immutable and is returned by getTotalTheoreticalSupplyAttoRep() . There is no administrator, post-deployment mint, allocation correction, or recovery entrypoint. An incorrect allocation must be fixed in the source list before deployment and the deterministic manifests must then be regenerated. Because constructor arguments are part of CREATE2 init code, any allocation change produces a new genesis REP address and new addresses for every deployment step whose constructor wiring depends on it. Never continue a deployment from a manifest generated for a different allocation list. A future release that intentionally changes starting holders must follow the same source review and full-manifest regeneration procedure.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"security-pool-guardrails","heading":"Security Pool Guardrails","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool creation, minting, withdrawal, and direct-ETH rules are collected here in contract-first form. Participant-controlled payout and share-receiving addresses must satisfy A22 asset-recipient compatibility . Deterministic identity depends on A27 cryptographic security , while vault and token authority depends on A28 account authority . Area Implementation behavior Source Origin pool shape Origin pools require an existing question, an unforked universe, a present universe REP token, and exactly two categorical labels in this order: Yes , then No . Statoblast adds Invalid as the third trading and resolution outcome. At construction, the effective escalation deposit is exactly max(1 REP, theoretical REP supply / 10,000,000) ; a new origin's non-decision threshold must exceed that effective value. A zero configured vault REP floor selects the default theoretical supply / 100,000 ; a nonzero constructor value is the exact override. The independently configured security-bond debt floor defaults to 1 ETH. SecurityPoolFactory.sol , ShareToken.sol , BinaryOutcomes.sol Deployment history The factory records security-pool deployments and exposes paged deployment-history reads for indexers and UIs. SecurityPoolFactory.sol Deterministic addresses SecurityPoolFactory derives securityPoolSalt = keccak256(abi.encode(parent, universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas)) , using a zero parent for an origin. The coordinator and child truth-auction factories each hash that value again with their caller ( SecurityPoolFactory ) for their CREATE2 salt. The pool deployment worker instead uses literal CREATE2 salt zero; its address varies through the full constructor init-code hash, which contains the pool wiring. An origin share token uses originId = keccak256(abi.encode(questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas, originUniverseId)) directly as its CREATE2 salt, while factory ownership, Zoltar, and question ID remain in its init code; children reuse that lineage token and inherit its priority fee. SecurityPoolFactory.sol , SecurityPoolDeployer.sol , PriceOracleManagerAndOperatorQueuerFactory.sol , UniformPriceDualCapBatchAuctionFactory.sol , ShareTokenFactory.sol Share-token salt squatting Direct ShareTokenFactory callers cannot reserve the canonical origin-pool share-token address. CREATE2 includes constructor arguments in the init-code hash, and the share token owner is msg.sender , so a direct caller using the canonical salt deploys a caller-owned token at a different address than the SecurityPoolFactory deployment. ShareTokenFactory.sol , ShareToken.sol Complete-set capacity Complete-set minting checks the next collateral amount against live ETH minting capacity derived from total REP capacity ownership, the current REP/ETH price, and the pool multiplier. It also requires any explicit unassigned auction position to remain healthy after the mint. Sold truth-auction ownership enters both total ownership and fee eligibility at finalization, so it accrues fees before the bidder claims it; its health affects minting, not fee accrual. Claiming later assigns that already-counted ownership and its fees to the bidder vault without adding it to either pool aggregate again. The pool updates accounting before the share-token mint call. SecurityPool.createCompleteSet , SecurityPool.depositRepToVault Fee accrual clamp Fee accrual is clamped to the question end time while the universe is unforked; after a universe fork, the accumulator is clamped to the fork time. SecurityPool.sol Fee accrual and rounding Fee-eligible capacity includes live vault-assigned ownership and sold truth-auction ownership awaiting claim. Settlement-collateral decay first enters an unallocated reserve; vault checkpoints and auction claims preserve fractional carry and move only whole, redeemable attoETH into totalClaimableVaultFeesAttoEth . totalAccruedFeesAttoEth() combines reserve and assigned claimable vault fees for settlement-collateral reconciliation. Global fee-index and sub-attoETH carries preserve value between accruals. Capacity-ownership-scoped index carry is cleared when pool financials are installed, ordinary vault capacity ownership changes, or liquidation transfers capacity ownership, so prior attribution dust is not reassigned. Assigning finalized-auction ownership to its winning vault is the exception: it preserves that carry because the sold ownership was already fee-eligible and already part of the denominator. After a fork permanently ends accrual, the pool tracks capacity ownership still awaiting the final index. Auction claims preserve aggregate fees; once every eligible vault and auction allocation is reconciled, a subsequent permissionless vault fee checkpoint returns any whole reserve attoETH that no vault can individually claim to settlement collateral. SecurityPool.updateSettlementCollateral , SecurityPool.updateVaultFees , SecurityPool._clearFeeIndexRemainder Retention-rate updates Retention-rate updates no-op when the pool is not Operational ; otherwise utilization divides tracked collateral by live ETH minting capacity derived from total capacity ownership. Unclaimed sold auction ownership therefore already affects utilization and fee accrual. A later claim changes only its vault assignment, not fee eligibility, total ownership, live capacity, or retention solely because of that claim. SecurityPool.sol Multiplier-preserving REP outflows withdrawRepFromVault rejects withdrawal while the vault still has REP escrowed in an escalation game. Withdrawals and new escalation deposits independently preserve multiplier-adjusted REP backing for the affected vault and aggregate pool at the latest valid REP/ETH price. SecurityPool.sol External fork withdrawal lock If the universe forked before the local escalation game ended and non-decision was not reached, parent-pool escalation withdrawal reverts. The child continuation already has the canonical snapshot and aggregate backing: winning inherited deposits settle there by proof, inherited losers require no transaction, and clearing the vault's unresolved parent escalation-deposit accounting is optional. SecurityPool.sol , EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep REP-to-backing-unit round-up The pool’s internal REP-to-backing-unit conversion uses ceiling division when it removes vault REP backing units for an escalation deposit. That intentionally removes enough REP backing units to cover the requested REP even when the conversion is fractional. SecurityPool._attoRepToBackingUnitsRoundUp , SecurityPool.depositToEscalationGame Escalation deposit wrapper depositToEscalationGame rejects pools with an inherited fixed outcome because they cannot enter another fork or safely unwind a later local non-decision. Otherwise it deploys the game on the first valid post-end deposit, previews the accepted amount, removes vault REP backing units with round-up accounting, checks local and global multiplier-adjusted backing, transfers REP into the game, and records the deposit. The game factory normally preserves the configured bond; if later REP burns reduce the live threshold to that bond or below, it uses nonDecisionThresholdAttoRep - 1 so long as the threshold exceeds one attoREP. SecurityPool.depositToEscalationGame , EscalationGameFactory.deployEscalationGame , EscalationGame.recordDepositFromSecurityPool Direct ETH receiver Ordinary calls to receive() accept ETH only from the forker, its truth auction, or its parent pool. Forced ETH can bypass receive() ; it remains raw, unaccounted surplus and is not collateral or accrued fees. SecurityPool.sol Vault enumeration getVaults(startIndex, count) pages the append-only vault registry in newest-registered-first order. Registration requires only a nonzero address and can occur without economic state. Consumers must read current REP backing, capacity ownership, claimable fees, escalation stake, and bad debt when deciding whether to display an entry. The call returns an empty array when count == 0 or the start index is out of range; getVaultCount() reports the registry length. SecurityPool.getVaults","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"share-migration","heading":"Share Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Share migration after a fork is user-facing asset migration, not vault REP migration. Canonical rules live in FORK-10 for persistent per-child materialization, SHARE-04 for the economic-claim denominator, and FORK-11 for unequal materialized supplies. Area Implementation behavior Source Persistent entitlement ShareToken.migrate preserves the caller's parent token balance as a branch-independent entitlement. After its first use that source balance is transfer-locked, preventing a seller and buyer from materializing the same claim. ShareToken.sol Target list The target outcome list must be non-empty, valid for the fork question, and strictly increasing. ShareToken.sol Canonical source and fork transition Migration requires a canonical source pool for the token's universe. If that pool is still Operational , migrate first asks its forker to initiate the pool fork; the call proceeds only after the source is PoolForked . That conditional transition can freeze the source pool and emit the ordinary pool-fork and snapshot events. ShareToken.migrate , SecurityPoolForker.initiateSecurityPoolFork Canonical destinations Every destination must be a canonical direct child of the source pool. A single-target migration may lazily create a missing child while the branch-creation window is open; a multi-target migration requires every canonical child pool to exist before the call. Lazy creation can also emit child-link, REP-migration, and continuation events. ShareToken.migrate , SecurityPoolForker.createChildUniverse Independent materialization Each selected child token id is minted up to the current source balance. A later call can select another existing child, and source shares added through an ancestor migration materialize only their previously unminted delta. ShareToken.sol Economic denominator Child setup copies the frozen parent's remaining economic claim supply rather than its currently materialized ERC-1155 supply. Complete-set minting and redemption update that denominator; share migration does not because its claims were reserved at fork time. SecurityPoolForker.sol , SecurityPool.sol Timing The eight-week window bounds Statoblast child-pool creation and pool-local vault/REP migration. Shares can materialize indefinitely in an already-created child. Raw Zoltar child-universe deployment and migration-REP splitting follow Zoltar's fork and balance guards, not this pool timestamp. ShareToken.sol , SecurityPoolForkerVaultMigrationBase.sol , Zoltar.sol Malformed outcomes Malformed fork outcomes are rejected using Zoltar question-data validation. ZoltarQuestionData.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"escalation-resolution-and-deposits","heading":"Escalation Resolution and Deposits","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Accepted deposits, edge-case resolution results, and carry-proof or residual-REP consumption all live in this section. Area Implementation behavior Source Deposit preview The preview path expects a proposed amount of at least the current startBondAttoRep . EscalationGame.previewDepositOnOutcome Recorded deposit amount The recorded deposit must be positive, and the accepted amount must be at least startBondAttoRep unless it exactly fills the selected outcome to nonDecisionThresholdAttoRep . EscalationGame.recordDepositFromSecurityPool , EscalationGameCalculations._getAcceptedDepositAmount Outcome room Accepted deposit amount is capped to the selected outcome's remaining room under nonDecisionThresholdAttoRep . EscalationGameCalculations._getAcceptedDepositAmount Tie adjustment If the accepted amount would create a tie with the current maximum balance while still below non-decision, the contract reduces the accepted amount by 1 attoREP ; if that breaks the accepted-amount rule, the deposit is rejected. EscalationGameCalculations._getAcceptedDepositAmount Unresolved resolution state If two or more outcomes meet the current running cost, getQuestionResolution() returns None . EscalationGameCalculations.getQuestionResolution Matching-fork continuation resolution After the continuation deadline, a game with a fixed child outcome settles deposits against that outcome. Payout settlement rejects any pool/game outcome mismatch. See Matching-question child outcome for the pool-level finality rule. EscalationGameCalculations.getQuestionResolution , EscalationGameSettlement._getPayoutQuestionResolution , SecurityPoolForker.getQuestionOutcome Empty-game fallback If all outcome balances are zero after the running cost is non-zero, getQuestionResolution() returns Invalid ; if the running cost is still zero, the unresolved check returns None first. EscalationGameCalculations.getQuestionResolution Strict leading resolution After the unresolved-cost check and the all-zero Invalid fallback, a strict Invalid , Yes , or No lead returns that outcome. Valid local deposits prevent tied maxima below non-decision by reducing the accepted amount by 1 attoREP , or reverting if that adjusted amount becomes invalid. Continuation snapshots preserve the parent balances exactly, including ties, so every selected branch starts from the same unresolved game state. EscalationGameCalculations.getQuestionResolution , EscalationGameCalculations._getStrictLeaderOrNone , EscalationGameCalculations._getAcceptedDepositAmount , EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances Structural non-decision predicate hasReachedNonDecision() becomes true when two or more outcomes reach nonDecisionThresholdAttoRep ; nonDecisionState separately records how that balance condition entered the lifecycle. New games use ⌈forkThresholdAttoRep / 2⌉ , so two threshold balances always contain at least the REP required to fund an own fork even when the fork threshold is odd. Zoltar.getNonDecisionThresholdAttoRep , EscalationGameCalculations.hasReachedNonDecision nonDecisionState = None No explicit non-decision transition has occurred. Deposits may remain available subject to the ordinary activation, continuation, timing, and amount guards. EscalationGame.previewDepositOnOutcome , EscalationGameDepositDelegate.recordDepositFromSecurityPool nonDecisionState = Local A local deposit brought a second outcome to the threshold. The game stores the real nonDecisionTimestamp , closes further deposits, and canTriggerOwnFork() returns true. That predicate is game-local: a pool with an inherited fixed outcome still rejects the fork transition in activateForkMode() . EscalationGameDepositDelegate.recordDepositFromSecurityPool , EscalationGameCalculations.canTriggerOwnFork , SecurityPool.activateForkMode nonDecisionState = InheritedThresholdTie Snapshot initialization preserved two or more threshold-full balances without fabricating a local timestamp. The game closes further deposits. With a fixed child outcome it follows the continuation clock and cannot trigger its own fork; without one, canTriggerOwnFork() returns true directly. EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances , EscalationGameCalculations.getEscalationGameEndDate , EscalationGameCalculations.canTriggerOwnFork Carry proofs Inherited carry uses Merkle Mountain Range peaks and nullifier roots so child games can consume proofs without replaying already-spent parent deposits. EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._verifyAndConsumeCarriedDepositProof Continuation withdrawal Anyone may relay a batch of winning carried proofs for one depositor after a child continuation resolves. A proof authenticates the original deposit and consumes its stable index once; the committed depositor remains the payout address. One mantissa/exponent ratio applies every intervening auction haircut exactly once to both proof amount and cumulative reward position without walking ancestors, and excludes losses before a continuation-local deposit was created. Consumption asks the immediate source game for retained cumulative-prefix differences, so per-leaf source-basis allocations telescope exactly to the aggregate checkpoint in every proof order. Inherited losing outcomes retire in constant-size work when the result is final and require no proof transaction; locally created losing deposits retain ordinary settlement. Child initialization freezes the carry root/count and retention metadata and performs no claim or owner import. The 64 MMR peaks are a logarithmic frontier, not a participant cap. Continuation resumes in one bounded call once aggregate backing is complete, and liquidation never processes or moves claims. SecurityPool.withdrawForkedEscalationDeposits , EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._consumeCarriedDeposit Optional unresolved parent escalation-deposit accounting cleanup The public SecurityPoolForker.migrateVaultWithUnresolvedEscalation wrapper first runs ordinary migration for that same vault: the parent REP backing-unit claim is converted to REP and credited as child-local backing units, capacity ownership moves to the child, claimable fees are checkpointed and retained in the parent vault, and proportional settlement collateral routes separately at pool level. Its fixed-size escalation cleanup then exports one Invalid / Yes / No principal tuple without another token transfer. The cleanup preserves proof leaves and neither funds dispute-staked REP backing nor authorizes inherited claims. SecurityPoolForker.migrateVaultWithUnresolvedEscalation , EscalationGameEscrow.exportVaultUnresolvedTotalsWithoutTransfer , EscalationGameForker.migrateVaultWithUnresolvedEscalation Residual sweep Once a game is final and unresolved principal is cleared, its residual can be settled. A continuation sweep first retires its inherited aggregate carry; after that, no local dispute stake or vault escrow may remain. An ordinary game's residual returns to the security pool. A fork continuation burns its entire residual, which can include inherited capital that must not accrue to child-local owners. This rule prevents capture by a late depositor and stranding in an ownerless fixed child. EscalationGameSettlement.sweepResidualRepToSecurityPool","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"fork-migration","heading":"Fork Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool-level migration mechanics after a universe fork live here: proxies, child-pool creation, pool-proxy REP splitting, and child outcome selection. Area Implementation behavior Source Pool-specific migration identity The forker lazily deploys one deterministic SecurityPoolMigrationProxy per parent pool. The proxy is the stable msg.sender for Zoltar migration accounting. REP sent to its predictable address before deployment remains isolated surplus: fork accounting uses newly routed REP and the Zoltar migration ledger, not the proxy’s preexisting ERC-20 balance. SecurityPoolForker.sol , SecurityPoolMigrationProxy.sol Proxy authority The migration proxy is owner-controlled by the forker and wraps lockRep , forkUniverse , splitToChild , and child REP sweeping. SecurityPoolMigrationProxy.sol Child REP backing During vault migration, the forker ensures the child pool is backed by enough child-universe REP for cumulative migrated REP credited to that child. SecurityPoolForkerVaultMigrationBase.sol Vault fees during migration Migration uses one fixed, fee-exclusive snapshot and a cumulative REP ceiling. The parent vault is checkpointed before its capacity ownership is cleared, so earned fees remain redeemable, and parent-to-child ETH transfers cannot consume the balance reserved by totalAccruedFeesAttoEth() . The Fork Migration Proportion and Fork Collateral Ceiling own checkpoint order, cumulative allocation, and repair derivation. SecurityPoolForker.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPool.sol Split shortfall tracking The forker tracks how much REP has already been split for each parent-pool/outcome pair and only splits the shortfall before sweeping child REP into the child pool. SecurityPoolForkerVaultMigrationBase.sol Canonical continuation snapshot Fork initialization stores the complete parent Invalid / Yes / No balances, carry totals, peaks, leaf counts, and nullifier roots once. Every lazily created child reads that same stored snapshot, even if an allowed parent claim occurs before a later child is created. SecurityPoolForker._snapshotEscalationAtFork , SecurityPoolForkerBase._initializeChildForkedEscalationGameIfNeeded , EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances Aggregate escalation backing An external unrelated fork reproduces the drained game's dispute-staked REP one-for-one in each selected child. The escalation's own fork transfers post-haircut aggregate backing instead; ordinary pool-held REP remains one-to-one. Each selected child receives its applicable aggregate backing once, and resumeFromFork remains paused until that backing is present after accounting for child REP already exported by valid direct pre-resume claims. See the canonical fork-migration derivation and FORK-08 for the raw and effective principal definitions and exact funding bound. Neither path scales or releases per-vault child escrow. SecurityPoolForker.initiateSecurityPoolFork , SecurityPoolForker.forkZoltarWithOwnEscalationGame , SecurityPoolForkerVaultMigrationBase._ensureChildEscalationBacking , EscalationGame.resumeFromFork Direct-claim replay protection A successful direct own-fork claim records both the stable parent deposit identity and cumulative claimed principal by outcome. Every current, late, or recursively inherited child rejects a second payout; effective inherited principal subtracts immediate-parent direct claims so those leaves cannot strand the residual sweep. EscalationGameForker.claimForkedEscalationDeposits , SecurityPoolForker.isEscalationDepositClaimedDirectly , EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep Optional vault cleanup See Optional unresolved parent escalation-deposit accounting cleanup for wrapper ordering and proof independence. In migration terms, the call records cleanup for one selected child and never processes another vault. SecurityPoolForker.migrateVaultWithUnresolvedEscalation , EscalationGameForker.migrateVaultWithUnresolvedEscalation Independent continuation liveness Child creation installs the canonical carry commitment, retention checkpoint, and aggregate backing without waiting for vault transactions or copying claims and owners. The fixed 64-peak MMR frontier is logarithmic commitment storage for up to 2^64 - 1 leaves, not a participant cap; see Merkle Mountain Range Carry Proofs . Once the child is operational and fully funded, resumeForkedEscalationGame resumes it in one bounded permissionless call. Authenticated winning proofs can then be relayed permissionlessly to pay their committed depositors, inherited losers retire without proofs, and optional parent cleanup may happen independently. SecurityPoolForkerBase._finalizeAwaitingForkContinuationIfReady , EscalationGameSettlement.withdrawDeposit , EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep Own-fork REP buckets When escalation triggers its own fork, escalationChildRepAtForkAttoRep equals disputeStakedRepToForkAttoRep - ⌊forkThresholdAttoRep / forkBurnDivisor⌋ . vaultRepAtForkAttoRep preserves ordinary pool-held REP one-for-one. Creating one selected child does not reduce the post-haircut escalation backing available to another selected child. SecurityPoolForker.forkZoltarWithOwnEscalationGame , SecurityPoolForker.getOwnForkRepBuckets , SecurityPoolForkerBase._initializeOwnForkRepBuckets Child-pool deployment window Child pools are created lazily for selected fork outcomes, but only while the parent pool is PoolForked and the eight-week migration window is still open. SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolUtils.sol Matching-question child outcome When the parent universe forks on the pool's question, the child stores its selected branch as a fixed result. depositToEscalationGame rejects new local deposits, and activateForkMode rejects every later pool fork transition. See Child Outcome Resolution for the collateral and REP-liveness rationale. SecurityPool.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolForker.sol Unrelated-fork child outcome A child created by an unrelated fork without an inherited fixed result uses a local escalation result only if that escalation ended before the universe forked; otherwise continuation or later state must produce the outcome. SecurityPoolForker.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"truth-auction-operations","heading":"Truth Auction Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Collateral-repair auctions have three operator-critical boundaries: forker ownership, a one-week bidding window, and paged settlement into vault accounting. Bids close at auctionStarted + AUCTION_TIME ; direct auction finalization is allowed at >= that boundary, but the public forker wrapper requires the boundary to have passed. The canonical clearing rules and examples are in Truth Auction . Area Implementation behavior Source Owner Child-pool truth auctions are owned by SecurityPoolForker . The direct auction startAuction and finalize calls are owner-only, while anyone can reach them through startTruthAuction and finalizeTruthAuction . UniformPriceDualCapBatchAuction.sol , SecurityPoolForker.sol Finalized settlement After finalization, only the auction owner withdraws bid outcomes from the auction; SecurityPoolForker wraps that call so anyone can settle a vault's bid pages. UniformPriceDualCapBatchAuction.sol , SecurityPoolForker.sol Child fee activation Completed migration or truth-auction settlement starts a new child fee epoch. Migrated ownership and sold truth-auction ownership enter the fee denominator at finalization. Each later auction claim assigns the bidder vault its ownership and fees accrued from the saved finalization index through the current index without changing the pool’s live eligible total; intervening capacity changes and liquidations remain preserved. SecurityPool.sol , SecurityPoolForker.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"repeth-oracle-operations","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The coordinator quick reference below covers staging, callback recovery, stale-operation handling, and liquidation boundaries. Report sizing, request cost, and current OpenOracle parameters are canonical in OpenOracle Integration . Area Implementation behavior Source Atomic initial report The sponsor funds the ETH bounty and WETH/REP position; the coordinator submits that position atomically as the initial reporter. A caller-selected WETH amount above the dynamic minimum is allowed. See OpenOracle sizing and funding for derivation, reporter withdrawals, application buffering, and escalation effects. OpenOraclePriceCoordinator.sol , openOracle.ts Immediate execution If a price is still valid, the operation executes immediately and positive unused ETH is refunded. A contract caller must accept the refund callback or the execution rolls back; the canonical refund warning also covers newly opened reports. OpenOraclePriceCoordinator.sol Fresh-price request guard requestPrice reverts while the cached coordinator price is still valid, so callers cannot open a redundant pending report on top of a fresh cache. OpenOraclePriceCoordinator.sol Staging guardrails Withdrawal and liquidation amounts must be non-zero. Validity must be positive and no more than five minutes. Withdrawals target the operator's own vault. Liquidation uses explicit operator, receiver, and target roles; receiver and target must differ. A delegated receiver requires a valid bounded approval and queue-time reservation, while the self-receiving operator path requires no signature. OpenOraclePriceCoordinator.sol Delegated receiver approvals LiquidationApprovalRegistry supports direct receiver approvals and EIP-712 permits from EOAs or ERC-1271 wallets. An approval binds pool, receiver, exact operator, exact or wildcard target, cumulative and per-operation ETH debt limits, minimum post-health factor, validity window, and nonce. Revocation blocks new reservations without disturbing pending ones; nonce invalidation blocks stale approvals. LiquidationApprovalRegistry.sol , openOracle.ts Approval reservation lifecycle Staging reserves at most requested debt, snapshotted target debt, per-operation allowance, and available cumulative allowance. Success consumes exactly moved debt and releases the remainder; bad-debt-only execution consumes zero. Every terminal failure and permissionless expiration releases once. Approval validity must cover the latest legal execution time. OpenOraclePriceCoordinator.sol , LiquidationApprovalRegistry.sol Pending report bound At most four operations are attached to one settlement callback. Operations can still be tracked as active even when they do not fit into the pending callback batch. OpenOraclePriceCoordinator.sol Sponsor exclusivity Once the cached price is stale and a caller funds a fresh coordinator report, only that pendingReportSponsor can append more staged operations until settlement. Valid disputes reset the settlement clock, so this paid exclusive lane can be extended indefinitely; the coordinator makes no bounded-availability guarantee. The economic tradeoffs , attack model , and parameter table own dispute funding, escalation, and rounding details. OpenOraclePriceCoordinator.sol , OpenOracle.sol High settlement basefee The callback clears the pending report and terminally fails every operation attached to that report if settlement basefee is above the stored maximum from request time. Each delegated-liquidation reservation is released. OpenOraclePriceCoordinator.sol Uneconomic or saturated final report Coordinator reports always enable dispute history. The callback reads storedGame(reportId).numReports . It rejects a saturated uint24 counter with Counter saturated ; otherwise it requires the final history record's WETH amount to cover the configured dispute-security formula at its recorded base fee plus the configured priority fee and rejects it with Report uneconomic . OpenOraclePriceCoordinator.sol , OpenOracle.sol Zero report values A callback with zero amount, zero denominator, or a computed zero price does not update lastPrice . OpenOraclePriceCoordinator.sol Recovery path Coordinator reports opt into OpenOracle's STORE_ALL and TRACK_DISPUTES flags. Recovery requires a pending report whose stored OpenOracle settlement timestamp is nonzero. It returns withdrawable reporter balances to the sponsor, clears the report, consumes all associated pending operations, and releases their liquidation reservations. An expired operation can also be cleaned permissionlessly before any valid-price requirement. OpenOraclePriceCoordinator.sol Consumed failures Expired operations, stale liquidations, zero-effect withdrawals, and liquidations too close to threshold are consumed and emitted as failed executions rather than retried forever. OpenOraclePriceCoordinator.sol Liquidation snapshot A staged liquidation becomes stale if target REP backing units or capacity ownership changes. The queue-time open-interest snapshot bounds the reservation and records context but is not itself a staleness key. Execution re-evaluates target and receiver health from live balances, live obligations, and the settled price. ETH debt moves only up to the amount whose complete 5% REP award is funded; REP-denominated capacity ownership moves proportionally. An unsuitable below-minimum receiver reverts instead of creating avoidable bad debt. See Queued Execution . OpenOraclePriceCoordinator.sol , SecurityPool.sol Liquidation distance A staged liquidation must remain at least minLiquidationPriceDistanceBps beyond the liquidation threshold when it executes. OpenOraclePriceCoordinator.sol , SecurityPoolLiquidationDelegate.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"support-module-inventory","heading":"Support Module Inventory","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The explanation focuses on protocol flow. The table below names the remaining support contracts that matter for integrators, code reviewers, and interface inventory work.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"caller-and-trust-boundaries","heading":"Caller and trust boundaries","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Deployment helpers are not interchangeable with canonical protocol discovery. Several are intentionally permissionless and namespace CREATE2 salts by msg.sender ; an application must discover canonical instances through SecurityPoolFactory , recognized pools, and recognized forker events rather than treating every factory-created address as canonical. Module Direct caller boundary Canonical-use boundary EscalationGameFactory deployEscalationGame and deployEscalationGameFromFork have no permission check, but a successful caller must implement the ISecurityPool reads used during construction; an EOA or incompatible contract reverts. The caller is bound into the new game as its securityPool ; the factory itself becomes the game's immutable owner and immediately calls start or startFromFork . Ordinary deployment requires a threshold above one attoREP and lowers a start bond at or above that threshold to threshold - 1 . The factory has no owner role and no later resumeFromFork relay. Accept a game only after EscalationGameSet from a recognized pool. In the supported continuation path, the owning pool calls resumeFromFork , which also verifies aggregate funding. ShareTokenFactory Anyone can call deployShareToken to deploy or retrieve a token under keccak256(abi.encode(msg.sender, salt, questionId)) ; that caller is the token's initial authorized address. Origin lineage tokens are the instances created by SecurityPoolFactory and named in DeploySecurityPool ; children reuse the parent's token. UniformPriceDualCapBatchAuctionFactory Anyone can call deployUniformPriceDualCapBatchAuction . Its salt is namespaced by caller and supplied salt, and its operational owner is the caller-supplied owner address. During external-universe initiation, the source must be authorized by its declared share token; own-game initiation does not perform that check, and neither relationship proves configured-factory registration. Child linking requires a deployed auction that this forker has never trusted before and preserves the source's factory and forker. Canonical pools remain the instances registered by the configured SecurityPoolFactory . PriceOracleManagerAndOperatorQueuerFactory Anyone can call deployPriceOracleManagerAndOperatorQueuer under a caller-namespaced salt with caller-supplied OpenOracle, REP token, and positive initialReportPriorityFeeAttoEthPerGas ; coordinator construction rejects zero and values that would consume the reserved OpenOracle uint128 report and escalation-halt capacity. The caller therefore chooses this immutable gas-price assumption within that bound. A pool coordinator is the instance named by canonical DeploySecurityPool ; SecurityPoolFactory atomically binds it to that pool before returning, and canonical child deployment inherits the value from the parent coordinator. SecurityPoolDeployer and SecurityPoolDeploymentWorker Only the configured SecurityPoolFactory may call the deployer; only that deployer may call its worker. They are code-size deployment plumbing. Pools become canonical only when the factory emits SecurityPoolRegistered and DeploySecurityPool . SecurityPoolMigrationProxy Only its immutable owner , the SecurityPoolForker that created it, may call lockRep , forkUniverse , splitToChild , or sweepChildRep . Its deterministic per-parent address is exposed by getMigrationProxyAddress and recorded in fork snapshot events; users do not call it. EscalationGameDepositDelegate , EscalationGameClaimDelegate , EscalationGameForker , SecurityPoolForkerVaultMigrationDelegate , and SecurityPoolLiquidationDelegate Their public methods are delegatecall implementations. The deposit delegate implements recordDeposit , recordForkedEscrowForOutcome , applyTruthAuctionHaircut , funding-gated resumeFromFork , consumeEscrowedRepForOwner , consumeUnresolvedRepForClaimOwners , creditClaimOwners , and creditExternalClaimOwners . The shared claim delegate handles retention checkpoint reads and initialization; it has no ownership or import surface. The forker delegates implement escalation and vault migration. The pool helper implements funded REP-backing-unit, capacity-ownership, receiver-debt, and full-request bad-debt liquidation accounting while isolating fees and claims, plus permissionless continuation resume. Direct calls operate on isolated or uninitialized helper storage and normally revert; they are not protocol actions. Accept resulting state and logs only when the recognized game, pool, or forker executes the matching wrapper through delegatecall . State changes and events then occur in that recognized contract's context. Canonical liquidation enters through SecurityPool.performLiquidation ; continuation progress enters through SecurityPool.resumeForkedEscalationGame . SecurityPoolEventEmitter.sol emitPoolAccountingCheckpoint , emitVaultAccountingCheckpoint , and emitForkSnapshotEvents are externally callable and payable , but direct calls execute against the helper's own storage and emit from the helper address. Payability permits delegatecalls from value-bearing protocol flows; callers must not send ETH directly because it is not protocol collateral and the helper has no recovery surface. Pools and the forker use constructor- or factory-installed modules through delegatecall , so logs are emitted from the recognized pool or forker address. Indexers must reject matching signatures from the helper or any unrecognized emitter. External-universe fork initiation requires declared-share-token authorization; own-game initiation does not. Child linking preserves the source factory and forker and rejects undeployed or previously trusted auctions. Configured-factory registration remains the canonicality boundary. Storage-layout tests protect the delegate's fixed slots. EscalationGameProofVerifier , MerkleMountainRange , and SecurityPoolUtils Stateless or library math and proof routines have no lifecycle authority. Public verifier calls can be used for previews, but do not mutate a game or pool. A result becomes protocol state only through a recognized game, pool, or forker transaction and its events. DeploymentStatusOracle Anyone can call getDeploymentMask ; the constructor alone fixes the ordered address list and emits DeploymentAddressesSet . It reports code presence only, not canonical wiring or readiness. Decode it using its constructor event or the matching deployment manifest order.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"securitypoolutils-read-surface","heading":"SecurityPoolUtils read surface","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"These external pure previews distinguish REP-denominated capacity ownership from ETH-denominated open interest. REP-per-ETH prices use PRICE_PRECISION , and health factors and security multipliers use basis points. The previews have no lifecycle, caller, freshness, or canonical-pool authority; only a recognized pool or coordinator transaction can apply their result. Function Exact boundary behavior Conceptual owner calculateFeeAccrual(settlementCollateralAttoEth, retentionRate, timeDelta, indexRemainder, feeEligibleCapacityOwnershipAttoRep, feesOwedRemainder) Applies fixed-point retention over elapsed time, carries the global index remainder, and returns whole credited fees plus the next fee remainder. Canonical callers avoid zero eligible capacity ownership before this division. Fee accrual calculateVaultFee(capacityOwnershipAttoRep, feeIndexDelta, remainder) Floors the vault's whole fee credit and returns the remaining fixed-point numerator for its next checkpoint. Fee accrual calculateMintingCapacityAttoEth(capacityOwnershipAttoRep, repEthPrice, securityMultiplierBps) Converts aggregate REP-denominated ownership into current ETH capacity using the live REP-per-ETH price and pool multiplier. Zero ownership or price returns zero. Dynamic capacity calculateVaultOpenInterestAttoEth(activeOpenInterestAttoEth, vaultCapacityOwnershipAttoRep, totalCapacityOwnershipAttoRep) Attributes live pool open interest pro rata to capacity ownership, rounding a positive vault share upward. Zero vault or total ownership returns zero. Dynamic capacity calculateBundledLiquidationTransfer(targetBackingUnits, targetCapacityOwnershipAttoRep, targetOpenInterestAttoEth, requestedDebtAttoEth, repEthPrice, currentPoolHeldAttoRepBalance, currentTotalRepBackingUnits, minimumRemainingAttoRep) Returns zero when the request, target open interest, capacity ownership, or price is zero. Caps a nominal debt quote by target open interest and by the complete 5%-bonus award fundable from target pool-held REP, then rounds proportional capacity ownership downward. Execution derives moved debt separately from the receiver's exact live open-interest increase. Partial requests preserve the configured REP minimum. On a full-target request, bad debt is target open interest minus exact moved debt and can include both an award-unfunded slice and integer-allocation residue. Vault liquidation isVaultHealthy(poolHeldVaultRepBackingAttoRep, disputeStakedAttoRep, openInterestAttoEth, repEthPrice, poolSecurityMultiplierBps) Checks the associated-REP and free pool-held REP requirements against live open interest. Both requirements round upward; zero open interest is healthy. isVaultHealthyAtFactor applies an approval-selected factor of at least 10,000 to both branches. Capacity and health calculateRetentionRate(settlementCollateralAttoEth, mintingCapacityAttoEth) Zero live minting capacity returns the maximum retention rate. Otherwise retention decreases linearly through the 80% utilization dip and remains at the minimum rate above it. Retention rate","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"production-contract-inventory","heading":"Production contract inventory","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Area Named contracts and modules Role Deployment tracking DeploymentStatusOracle.sol Reports one code-presence bit per configured deployment-step address. See Deployment Status Oracle . Carry proof hashing MerkleMountainRange.sol , EscalationGameProofVerifier.sol , EscalationGameTypes.sol Defines the carried-deposit leaf shape, the 64-peak limit, proof-length rules, and nullifier-root replay protection. See Merkle Mountain Range carry proofs . Escalation game composition EscalationGame.sol , EscalationGameCalculations.sol , EscalationGameCarry.sol , EscalationGameClaimDelegate.sol , EscalationGameDepositDelegate.sol , EscalationGameEscrow.sol , EscalationGameSettlement.sol , EscalationGameState.sol , EscalationGameStorage.sol Splits the game across immutable ownership and storage, claim and deposit delegation, calculations, continuation proofs, escrow, and settlement without exposing those internal modules as separate canonical games. ERC-20 support ERC20.sol , IERC20.sol , IERC20Metadata.sol , Context.sol , SafeERC20Ops.sol , GenesisReputationToken.sol , ReputationToken.sol Base REP token implementation, interfaces, metadata, execution context, safe transfer wrappers, the constructor-allocated genesis token with fixed theoretical supply, and the inherited transfer , approve , and transferFrom entrypoints. ERC-1155 and token ids ERC1155.sol , ShareToken.sol , TokenId.sol , IERC1155.sol , IERC1155Receiver.sol , IERC165.sol , IShareToken.sol Outcome-share token plumbing, interface support, token-id encoding, inherited setApprovalForAll , and both single and batch safeTransferFrom forms. Factories and deployers EscalationGameFactory.sol , SecurityPoolFactory.sol , SecurityPoolDeployer.sol , ShareTokenFactory.sol , UniformPriceDualCapBatchAuctionFactory.sol , PriceOracleManagerAndOperatorQueuerFactory.sol Deterministic deployment entrypoints for pool, share-token, oracle-coordinator, escalation-game, and auction instances. Migration, liquidation, and storage modules SecurityPoolStorage.sol , SecurityPoolLiquidationDelegate.sol , SecurityPoolMigrationProxy.sol , SecurityPoolForker.sol , SecurityPoolForkerAuctionSettlementBase.sol , SecurityPoolForkerBase.sol , SecurityPoolForkerStorage.sol , SecurityPoolForkerTypes.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolForkerVaultMigrationDelegate.sol , EscalationGameForker.sol , SecurityPoolEventEmitter.sol , LiquidationApprovalRegistry.sol , SignatureValidation.sol Defines shared pool storage, delegated liquidation accounting and bounded approval reservations, ECDSA/ERC-1271 signature validation, funded continuation delegatecalls, fork-time state, truth-auction settlement, vault and unresolved-escalation migration, event encoding, and stable proxy identity used while routing parent state into child pools. Protocol interfaces IEscalationGame.sol , ISecurityPool.sol , ISecurityPoolForker.sol , ISecurityPoolForkerChildEscalationGameInitializer.sol , IUniformPriceDualCapBatchAuction.sol , IWeth9.sol , IAugur.sol Typed boundaries used by games, pools, migration delegates, auctions, WETH integration, and external Augur compatibility. Protocol utilities Constants.sol , ScalarOutcomes.sol , BinaryOutcomes.sol , SecurityPoolUtils.sol , Multicall3.sol , WETH9.sol Shared constants and math, outcome representations, Multicall3 aggregate , tryAggregate , tryBlockAndAggregate , blockAndAggregate , aggregate3 , aggregate3Value , and block-context reads, plus standard WETH9 deposit , withdraw , approval, and transfer support. Trading protocol PredeploymentShareSink.sol , TwoWayConstantProductFactory.sol , TwoWayConstantProductMath.sol , TwoWayConstantProductPair.sol , TwoWayConstantProductRouter.sol , ITradingShareToken.sol , ITwoWayConstantProductFactory.sol , ITwoWayConstantProductPair.sol . Deterministic two-outcome pool deployment, constant-product pricing and reserves, routed liquidity and swap operations, canonical Statoblast share-token integration, and quarantine of shares sent to counterfactual pair addresses before deployment. Imported oracle integration UPSTREAM.md , OpenOracle.sol , Errors.sol , ISignatureTransfer.sol , interfaces/IERC20.sol , interfaces/IERC165.sol , token/ERC20/IERC20.sol , IERC1363.sol , SafeERC20.sol , Panic.sol , ReentrancyGuard.sol , StorageSlot.sol , utils/introspection/IERC165.sol , Math.sol , SafeCast.sol OpenOracle's imported source, exact SlimStorage revision, compiler profile, and dependency provenance are owned by UPSTREAM.md . The coordinator submits attoETH/attoREP reports with dispute history and full game storage enabled; OpenOracle's packed event stream remains separate from Zoltar replay.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"","heading":"","keywords":["deployment","bitmask","runtime bytecode","status"],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"DeploymentStatusOracle is a deployment-progress helper. It does not measure protocol readiness or initialization success. It only reports which configured deployment-step addresses currently contain runtime bytecode. The canonical mainnet and Sepolia manifests list this contract directly, so operators and frontends need a precise explanation of the returned bitmask and the built-in 256-step ceiling.","title":"Deployment status oracle","topic":"Deployment","weight":1},{"fragment":"mask","heading":"What getDeploymentMask() Returns","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor stores one ordered address[] deploymentAddresses . getDeploymentMask() loops over that array and checks deploymentAddresses[index].code.length . When code exists, the oracle sets bit index in the returned uint256 . This is a code-presence bitmap. A set bit means code exists at the configured address. A clear bit means the address currently has no code. The oracle does not verify constructor args, ownership wiring, or post-deployment setup. A row of deployment steps maps by index into bit positions in a uint256 mask. Each step sets its bit only when code exists at that configured address. Deployment Status Mask Each configured deployment step uses one bit position in the returned word. The oracle sets that bit only if the step's address currently contains code. deployedMask = ∑ i = 0 codePresent ( i ) ⋅ 2 i Each configured deployment step contributes one bit position to the returned word.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"ordering","heading":"How Bits Map To Deployment Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Bit positions are array positions. Bit 0 maps to the first constructor address, bit 1 to the second, and so on. Offchain decoding must use the same ordered list that seeded the oracle. The constructor emits DeploymentAddressesSet(address[] deploymentAddresses) with that exact ordered list. It is the onchain source for recovering a particular oracle instance's bit mapping; the manifest below describes the current planned deployment and must match the constructor event for that instance. The current UI and deployment helpers derive the constructor list from deploymentSteps with one exception: the oracle does not include its own address in its constructor array. The manifest still lists deploymentStatusOracle as a deployment step, but the UI tracks that step out-of-band and starts consuming mask bits from the remaining addresses. The tables below render the complete bit mappings directly from the canonical mainnet and Sepolia deployment manifests, excluding deploymentStatusOracle . Each manifest's ordered deploymentSteps array is the canonical mapping for that network. Bit Mainnet step id Label Status in entered mask Loading the canonical manifest… Bit Sepolia step id Label Loading the canonical manifest… If the manifest's constructor order changes, the rendered bit meanings change with it; this page does not maintain a second list. The manifest's derivedContracts list is separate. Those addresses are not part of the deployment-status mask unless they also appear in the constructor array. Decode a deployment mask Mask value Loading the canonical mapping… Decoder controls will be available when the mapping loads. Retry loading the canonical mapping","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"limit","heading":"Why The Cap Is 256 Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor requires deploymentAddresses.length <= uint256(type(uint8).max) + 1 , which is 256 . The limit exists because the result is a single uint256 , and each tracked step consumes one bit. Indices 0...255 are supported. A deployment process with more than 256 tracked addresses must split status across multiple masks or use a different representation.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Interpretation and Verification Boundaries","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Evidence What it proves What it does not prove Bit set Runtime bytecode exists at the configured address. Correct bytecode, constructor values, wiring, permissions, or operational readiness. Bit clear No runtime bytecode was observed at that address by this call. Whether the address is intentionally empty or the manifest is wrong. Manifest match The decoder used the intended ordered address list for the network. That deployed code matches the release or that RPC responses are honest. Full deployment verification Code hashes, required wiring, permissions, and manifest provenance can be checked together. Economic safety or future operational liveness. This page documents the deployment-status bitmask only; it does not provide complete bytecode, wiring, permission, or manifest verification. The getDeploymentMask() return type is uint256 ; the constructor reverts when more than 256 addresses are supplied.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Static decoder test vectors","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Mask Meaning 0x0 No tracked constructor address contains runtime code. 0x1 Only constructor address index 0 is present. 0x5 Constructor address indices 0 and 2 are present. 2^255 Only the highest supported bit, index 255, is present. The interactive tables require JavaScript. The ordered source lists are available in the Mainnet and Sepolia manifests; bit i always corresponds to the i th constructor address after excluding deploymentStatusOracle .","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"","heading":"","keywords":["MMR","carry proof","hashing","nullifier"],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Statoblast uses a Merkle Mountain Range only for inherited escalation carry. Parent escalation games export unresolved deposits into compact snapshots, and child continuations later verify withdrawals against those snapshots without replaying the full parent history onchain. The hashing primitives live in MerkleMountainRange.sol . Snapshot storage, proof structs, peak bounds, and replay protection live in EscalationGameTypes.sol , EscalationGameCarry.sol , and EscalationGameProofVerifier.sol .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":1},{"fragment":"leaf-shape","heading":"Leaf Shape And Hash Order","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each leaf hashes one unresolved deposit with depositor , outcome , amount , parentDepositIndex , cumulativeAmount , and sourceNodeId . Leaf hashes use keccak256(abi.encode(...)) . Parent hashes use keccak256(abi.encodePacked(left, right)) . Field Why it is committed depositor Binds the proof and payout to the original depositor. This ownership is immutable: liquidation moves only pool-held vault REP backing and cannot acquire or split the committed escalation claim. outcome Separates invalid, yes, and no carry domains. amount Commits to the source principal. parentDepositIndex Provides the stable identity later consumed by nullifiers. cumulativeAmount Preserves payout-order prefix data. sourceNodeId Distinguishes otherwise identical leaves copied from different local nodes. Hash order matters. Internal nodes always hash left before right , so proofs are position-sensitive inside each peak.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Normative Encoding","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The leaf ABI types are (address,uint8,uint256,uint256,uint256,uint256) , in the order depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId . outcome is the Solidity enum ordinal. Leaf hashing uses standard ABI word padding through abi.encode ; parent hashing uses the 64 raw bytes from abi.encodePacked(left, right) . There is no additional domain separator. leafHash = keccak256(abi.encode(depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId)) parentHash = keccak256(abi.encodePacked(left, right)) root = bagPeaks(occupiedPeaks, peakCount) Proof verification rejects an empty or out-of-range peak, leafIndex >= 2^peakHeight , a sibling array whose length is not peakHeight + peakCount - 1 , a nullifier path whose length is not 64 , a root mismatch, or an already-consumed nullifier.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Two-leaf conformance vector","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Input or intermediate Value Leaf 0 ABI values 0x0000000000000000000000000000000000000001, 0, 1, 0, 1, 0 Leaf 0 hash 0xb01042d963a0c261d64893dfb7e1f221c93ef608653cb92f9ca2c8899ca4572b Leaf 1 ABI values 0x0000000000000000000000000000000000000002, 1, 2, 1, 2, 1 Leaf 1 hash 0xe0ca39e8852dc96783e8b8f0859cfbf155338f9144ba4b9fdd6af847299fc867 Peak 1 / root 0x1c9c7f3c2c8bebf92ce393deb5dccdd253f2b10084af72defb5dcd4486832b27","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"peaks","heading":"Peaks And The 64-Peak Bound","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The carry snapshot stores one peak per set bit in the leaf count. The system fixes MERKLE_MOUNTAIN_RANGE_MAX_PEAKS = 64 , and carry initialization requires each snapshot leaf count to be less than 2^64 . That does not cap the snapshot at 64 leaves. It caps the number of peak positions. Any leaf count from 0 up to but not including 2^64 is valid because its binary decomposition fits inside 64 peak slots. A leaf count of thirteen is shown as binary one one zero one, corresponding to occupied peaks at heights zero, two, and three which are later bagged into one root. MMR Peaks The occupied peak heights are exactly the set bits in the snapshot leaf count. Those occupied peaks are later bagged into one root. To form one root, the verifier collects occupied peaks in ascending height order and then bags them from right to left with bagPeaks() . snapshotLeafCount < 2 64 The 64-peak constant means each inherited snapshot leaf count must fit below 2^64 . Local carry appends behave like binary addition with carries: a new leaf merges upward through occupied lower peaks until it finds the first empty peak slot. If that upward carry would reach height 64 , the append reverts with MMR too tall .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"proofs","heading":"Proof Structure","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"A carried-deposit proof has two parts. First it proves membership in the inherited MMR snapshot for one outcome. Then it proves that the same parentDepositIndex has not already been consumed in this continuation's nullifier tree. Component Purpose leafIndex , merkleMountainRangePeakIndex , merkleMountainRangeSiblings Proves the carried deposit belongs to the inherited snapshot root for that outcome. nullifierSiblings Proves the nullifier leaf is still empty so the carried deposit cannot be replayed. The membership lane combines the carried deposit leaf with bottom-up siblings inside its selected peak, then with other occupied peak roots in ascending height order to reconstruct the snapshot root. The nullifier lane hashes the stable parent deposit index and combines it with a fixed-depth sibling path to prove the claim has not already been consumed. Carry Proof Anatomy The MMR sibling array first reconstructs one selected peak and then supplies every other occupied peak. The independent 64-level nullifier path prevents the same stable deposit identity from being consumed twice. The index semantics are stricter than the field names might suggest. merkleMountainRangePeakIndex is the occupied peak height, not the ordinal position of that peak among occupied peaks. leafIndex is the leaf's offset inside that selected peak, so the verifier requires leafIndex < 2^merkleMountainRangePeakIndex . For example, if the full snapshot has 13 leaves, its occupied peak heights are 0 , 2 , and 3 . A proof for a leaf inside the height- 2 peak uses merkleMountainRangePeakIndex = 2 and a peak-local leafIndex in 0...3 , not the leaf's global index across all 13 leaves. merkleMountainRangeSiblings is also ordered in two phases. The first merkleMountainRangePeakIndex entries are the bottom-up Merkle siblings inside the selected peak. The remaining entries are the other occupied peak roots in ascending peak-height order, skipping the selected peak height. That exact ordering is why the verifier checks merkleMountainRangeSiblings.length == peakHeight + peakCount - 1 . Separately, the verifier requires nullifierSiblings.length == NULLIFIER_DEPTH with NULLIFIER_DEPTH = 64 . Nullifier paths are keyed by uint256(keccak256(abi.encode(parentDepositIndex))) , so the stable parent deposit index is the replay-prevention identity. Plan a carry proof Choose a snapshot leaf count and one occupied peak. The planner reports the peak-local index bound and the exact sibling-array lengths enforced by the verifier. It does not construct hashes or replace an offchain proof builder. Snapshot leaf count Occupied peak height Height 2 Peak-local leaf index Binary leaf count 1101₂ Occupied peak heights 0, 2, 3 Selected peak capacity 4 leaves; local indexes 0…3 MMR sibling hashes required 4 Nullifier sibling hashes required 64 Selection Valid peak-local index","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"snapshots","heading":"Snapshots In Child Continuations","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each continuation outcome stores both an inherited snapshot baseline and a mutable current state. Field Meaning snapshotLeafCount and snapshotPeaks The immutable inherited carry commitment. currentLeafCount and currentPeaks The descendant carry snapshot after inherited snapshot initialization plus local carry appends and local carry-leaf removal. Inherited proof consumption is tracked by currentNullifierRoot and unresolved totals rather than by mutating these peak fields directly. inheritedUnresolvedTotalAttoRep and localUnresolvedTotalAttoRep The principal totals the current carry state must still represent. currentNullifierRoot The replay-protection root after any carried proof has been consumed. The continuation API exposes the current descendant carry snapshot through getForkCarrySnapshot() and pages only local unresolved leaves through getCarryLeafPageByOutcome() . The immutable inherited baseline remains in snapshotPeaks and snapshotLeafCount inside outcome state, while getForkCarrySnapshot() reports the current carry peaks, current leaf counts, current totals, and current nullifier roots after local appends and proof consumption.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"sources","heading":"Sources","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"MerkleMountainRange.sol EscalationGameProofVerifier.sol EscalationGameCarry.sol EscalationGameTypes.sol","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"","heading":"","keywords":["Statoblast","markets","security pools","overview"],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Prediction Market draft This white paper develops the Statoblast prediction-market design, its REP-backed resolution path, and its security assumptions. The documentation overview introduces the system; the sections below provide the detailed protocol model and accounting rules. Related topics: documentation overview , escalation game , Zoltar , and security model . It is designed so that: Honest reporting is economically rewarded. Dishonest reporting becomes increasingly expensive. Anyone can challenge an incorrect resolution by risking REP. Markets continue operating even when participants fundamentally disagree. Every trader can ultimately settle their position according to the outcome they believe is truthful, even if multiple realities continue to exist simultaneously. Statoblasts design is based on Artic Tern Oracle and Sisyphean Exchnge .","title":"Statoblast protocol","topic":"Protocol","weight":1},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast consists of two separate protocol identities. Zoltar is the base forking oracle layer for universes, questions, and REP branching. Augur Statoblast is the prediction market layer that adds market collateral (open interest), local disputes and fork resolution mechanisms Statoblast's lifecycle starts by creating a pool scoped to one question and universe, letting traders use it and vault owners fund its security vaults, trying to resolve disputes locally, and only then turning to fork. A question is registered in Zoltar and a Statoblast security pool is deployed for that question in one universe. Vault owners deposit REP into security vaults and select a target factor for each deposit. That instruction determines only the incremental REP-denominated capacity created by that deposit. The current REP/ETH price converts the pool's aggregate ownership into live ETH minting capacity without iterating through vaults. After the market ends, REP reporters try to resolve the market outcome locally through the escalation game. In the unfortunate case where the escalation game raises sufficient amount of REP without reaching a decision, Statoblast triggers a Zoltar fork. An unrelated external Zoltar fork can also interrupt the pool before local resolution. After a fork occurs in Zoltar, a migration period begins in each Statoblast pool. Each vault holder chooses a market outcome, converts the parent backing-unit claim to REP in that child, and routes a proportional share of that pool's settlement collateral . If a child pool is missing ETH collateral after migration, it sells child-universe REP through a Truth Auction to repair that collateral gap. The child pool in the branch users choose to keep using resumes operation and users settle or redeem positions there. The lifecycle is shown in the system decision-flow diagram below: operation leads to local escalation, then either settlement or fork migration, auction repair, and child settlement.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"2. Protocol participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"#","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Traders / Liquidity providers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit ETH to mint complete sets containing transferable Yes , No , and Invalid outcome shares. Pay time-based fees in ETH to vault owners for the duration that the complete sets remain outstanding. Trade by splitting complete sets into their individual outcome shares. This trading occurs outside the protocol. Burn complete sets to recover ETH before resolution, or redeem individual winning shares for ETH after the market resolves.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Vault owners","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit REP into a security pool. Provide REP-backed security capacity for traders. Receive time-based fees in ETH from traders holding outstanding complete sets. Participate in the Escalation Game after the market ends. Choose the child where their parent REP claim becomes child-local backing units and receives its allocated share of pool settlement collateral after a fork. Keep the value of their REP backing above the amount required to secure their allocated open interest. Trigger OpenOracle games when a REP/ETH price is required for a protocol operation.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"REP holders","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Decide whether to create a security pool for a question. Decide whether to participate in an existing security pool. Split their REP into outcome-specific child-universe REP during a Zoltar fork.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"OpenOracle disputers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"OpenOracle disputers, such as arbitrage bots, monitor active OpenOracle games and challenge inaccurate REP/ETH prices when doing so offers a profitable arbitrage opportunity.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Truth auction participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"After a Zoltar fork, truth auction participants provide ETH to acquire REP-backed positions from an auction. Their ETH helps repair missing settlement collateral in a child security pool.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Liquidation receivers and operators","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"A receiver vault accepts moved security-bond debt and receives the REP backing-unit and capacity-ownership award. An operator monitors vault health, submits the liquidation, and pays its gas and oracle costs; the protocol does not compensate the operator merely for submitting. The same account can fill both roles through the self-receiving route, or a receiver can grant a bounded approval for a separate operator. See Capacity and delegated liquidation for the complete role, approval, price-distance, and execution lifecycle. Base required REP is the vault's live proportional open interest multiplied by the settled REP-per-ETH price, rounded upward. A target fails ordinary health when either required backing branch fails: pool-held REP + dispute-staked REP ≥ pool security multiplier × base required REP pool-held REP ≥ max(migration security multiplier, liquidation reserve multiplier) × base required REP The implementation rounds each required-REP branch upward. The ordinary target-health check applies a 10,000 BPS factor. Liquidation eligibility additionally requires the configured minimum price distance and the operation's other live guards. A delegated approval may demand a higher factor on both branches for the receiver's post-transfer health; it does not raise the target's liquidation threshold.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"3. Security Pools","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# The purpose of security pools is to ensure that all open interest is supported by sufficient REP. Aggregate capacity limits new minting, while each vault's live safety independently requires both the associated-REP and pool-held-REP backing branches described below. This requirement protects outcome-share holders by making dishonest behavior economically unprofitable. If a vault owner attempts to manipulate the market's resolution, the REP they risk losing should be worth more than any potential gain. The protocol therefore rewards behavior that supports correct resolution and penalizes behavior that threatens it.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"dynamic-capacity","heading":"Deposit Target and Dynamic Capacity","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Each positive REP deposit made for fee-earning capacity includes a deposit target factor of at least 10,000 BPS . The deposit adds ⌊deposited REP × 10,000 / deposit target factor⌋ of REP-denominated capacity ownership. Solidity rounds this quotient downward, so an extremely high target factor can make a valid deposit add zero attoREP of ownership. Deposits with different targets combine in one vault; the latest instruction is not a vault-wide target or health measurement. Capacity ownership aggregates at pool level in constant time and determines the vault's fee share. The pool's one Statoblast security multiplier and the current REP/ETH oracle quote convert total capacity ownership into ETH minting capacity: capacity ownership ÷ REP per ETH ÷ security multiplier . A higher REP-per-ETH quote means each REP is worth less ETH and therefore lowers live ETH capacity; a lower quote raises it. Neither change rewrites vaults or erases existing open interest. Vault health and liquidation instead compare live obligations against the vault's actual REP and proportional capacity ownership at the current price. The associated-REP-per-capacity ratio counts pool-held REP plus REP attributed to the vault in the current escalation game. That dispute stake is at-risk security, not liquid collateral with a known payout. The pool-held-REP-per-capacity ratio counts only REP immediately held by the pool. Neither ratio is current vault health: current health also depends on proportional open interest, the REP/ETH price, the security multiplier, and both the associated-REP and pool-held-REP constraints. Withdrawals burn capacity ownership in proportion to the REP removed. They update backing and capacity without selecting a new deposit target.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Fee Extraction and Retention Rate","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Being vault holder is valuable as it gives a permission to earn fees from traders that are holding complete sets or share tokens. Simply by holding REP, you are not entitled any fees in the system. Fee extraction and retention vary with vault capacity ownership and utilization; see Open Interest Fees for the complete fee curve and epoch accounting. read more from here on how fees work","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Shares and Complete Sets","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"ShareToken is an ERC-1155 contract. Each complete set mints one Invalid , one Yes , and one No share. The token id encodes the universe id and outcome, making positions fork-aware. ETH mints Invalid, Yes, and No shares. Operational pools redeem full sets, and finalized winning shares redeem through outcome settlement. Share Lifecycle Complete sets mint one Invalid, Yes, and No share; operational redemption burns the complete set, while finalized settlement redeems the winning outcome.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"escalation","heading":"4. Escalation Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# When a market ends, Statoblast attempts to resolve its outcome by running an Escalation Game based on The Isonzo Front . Participants escrow vault-backed REP behind Invalid , Yes , or No . Each deposit adds to that outcome's cumulative balance; it does not have to outbid the previous deposit or the current leader. The accepted deposit must normally meet the game's starting bond, unless it fills the selected outcome to the non-decision threshold. The cumulative binding-capital threshold increases with elapsed time, and the median outcome balance determines the scheduled end. Individual deposits still use the fixed start bond. After that deadline, a strict balance leader resolves the question. If two outcomes instead reach the non-decision threshold, the game enters local non-decision and Statoblast proceeds to its fork-resolution path. Why not fork immediately? A fork asks the whole universe to branch, so Statoblast first gives vault-backed REP a local path to settle the question. The rising cost curve makes late disagreement increasingly expensive, while the non-decision threshold keeps the fork path available when competing outcomes remain strongly funded. The Escalation Game system is an optimization, and in theory the system would work without it as well, in a more inefficient manner. read more from here on escalation game works","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"7. Forks and Migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# If the Escalation Game fails to reach consensus and enough REP has been committed to satisfy Zoltar's fork threshold, the game triggers a Zoltar fork and the fork resolution process begins. A pool can also enter fork resolution when another Zoltar application forks the same universe. The pool then migrates to the child universes, runs a truth auction if needed, and continues there. A pool moves from active operation through fork, child deployment, REP and collateral migration, and any auction repair to an operational child. Fork Timeline The parent pool pauses, vault owners select child universes, and each child resumes only after migration and any required collateral repair. Fork Steps The parent pool enters fork mode. Anyone can deploy a child pool for a valid fork outcome. Vault owners can convert their parent backing-unit claim to REP, credit that amount as child-local backing units, and move capacity ownership to one child of their choosing. Claimable fees remain checkpointed, funded, and redeemable in the parent vault. Unresolved ongoing escalation games fork into child pools and the games continue after a pause The child starts a truth auction after the migration deadline if collateral is missing. After the truth auction ends, winning bidders settle into REP backing units and capacity ownership. Vault owners claim winning own-fork escalation deposits; the own-fork path is open only during the parent migration window. A successful direct claim invalidates that stable deposit identity in every current and future child continuation. Shares migrate into new universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"8 week migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Vault holders have eight weeks to choose a child universe. Migration happens one pool at a time: a vault's parent backing units determine both its snapshot REP claim and its share of that pool's settlement collateral. The REP claim becomes child-local backing units. No collateral moves from other pools or markets in the universe. During the migration period, vault owners choose which child universe to support by migrating their positions into the corresponding child security vault. This preserves disagreement by splitting the parent market into separate branches. However, ETH is not duplicated across those branches. Each child pool receives only the pool-level settlement collateral routed to it, transferred capacity ownership, and converted child-universe REP backing credited as child-local units. For this mechanism to remain secure, the value of the pool-held REP backing must exceed the value of the pool settlement collateral it moves. Otherwise, a malicious vault could shift a significant portion of this pool's collateral to a universe in which they hold favorable positions. Example: backing-unit and settlement-collateral migration Bob's vault has 5 backing units out of 20 backing units in this pool. At the fork snapshot, the parent pool has 40 REP represented by those 20 units and 10 ETH of settlement collateral, so this example uses a 2 REP per backing-unit conversion. When the oracle forks into REP A and REP B , Bob believes universe A is correct and migrates to universe A. As a result: Bob's vault receives 10 child-local backing units , equal to the 10 REP A produced by the snapshot's 2 REP per parent backing-unit conversion. A proportional share of this pool's settlement collateral moves to universe A: 5 backing units 20 backing units × 10 ETH = 2.5 ETH Bob's 5 of 20 pool backing units route 25% of this pool's 10 ETH settlement collateral, or 2.5 ETH, to the selected child. The remaining vault owners choose universe B. Only this pool's collateral is divided; other markets and pools are unaffected. Their vaults receive the remaining 30 child-local backing units , equal to the corresponding 30 REP B . The remaining proportional share of this pool's settlement collateral moves to universe B: 15 backing units 20 backing units × 10 ETH = 7.5 ETH The remaining 15 of 20 pool backing units route the remaining 7.5 ETH of this pool's settlement collateral to universe B. After migration: Universe A credits Bob with 10 child-local backing units and receives 2.5 ETH of this pool's settlement collateral. Universe B credits the other owners with 30 child-local backing units and receives 7.5 ETH of this pool's settlement collateral. Escalation principal, rewards, carried claims, and externally supplied dispute-staked REP have zero parent-OI migration power. A losing participant would otherwise rationally route dispute-staked REP toward a false child merely to preserve valuable OI attached to it. Only pool-held vault REP backing determines a vault's migration weight. vaultMigrationPoweredAttoRep = vaultTotalAssociatedAttoRep − vaultEscalationGameAttoRep Escalation claims are excluded. Migration safety uses only pool-held vault REP backing. The effective multiplier is floored at 10,500 BPS so free backing normally funds the complete liquidation award. The exact live health rules are owned by the liquidation design . This separation lets REP perform escalation work without letting contingent claims secure parent open interest. A vault's settlement-collateral migration weight is its pool-held vault REP backing share of all pool-held vault REP backing at the fork. migrateVault converts the parent backing-unit claim to REP, credits that REP amount as child-local backing units, transfers capacity ownership to one selected child, and clears both parent fields. The REP-backing share separately routes proportional pool-level settlement collateral, while the transferred capacity ownership determines the vault's live proportional open-interest allocation in the child. Claimable fees are checkpointed and remain funded and redeemable in the parent vault. The vault cannot divide or reuse its migration state across sibling universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"child-outcome-resolution","heading":"Child Outcome Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Child pools resolve outcomes through the forker. When the parent universe forks on the pool's question, each child stores its selected Zoltar outcome as the fixed result, whether the fork began through the pool-specific path or a direct Zoltar call. The pool stores and reports that result from child creation, and it is final for that pool. Pool asset redemptions begin after the child becomes operational. Question, pool, escalation, fork, child migration, auction repair, fixed-outcome settlement, and unresolved recursive continuation flow. System Decision Flow The lifecycle connects question registration, pool operation, local escalation, fork migration, auction repair, settlement, and recursive continuation when a child remains unresolved.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"Shares migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"When a fork occurs, share migration locks the source balance and accepts an ordered list of child targets. The first materialization gives each untouched selected child the full current source balance; later calls mint only that child's unmaterialized delta. Shares do not disappear from the source or split into a pro-rata remainder. On first materialization into untouched children, the full current parent balance materializes in each selected child token id while the source remains locked as an entitlement. Later calls mint only the unmaterialized delta for each child. Share Migration Share migration is not a pro-rata split across selected branches. The source balance remains as a transfer-locked entitlement after first use, and each untouched selected child first materializes the full current balance. Later calls mint only that child's unmaterialized delta.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"auction","heading":"8. Truth Auction","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# After migration, a child pool may still have less ETH than it needs to support its open interest. A Truth Auction can sell some child-universe REP for ETH to repair that shortfall. The auction has an ETH raise target and a cap on how much REP it may sell. The cap depends on migrated REP, pool-held REP, and unresolved dispute-staked REP; it may allow the full auctionable amount, reserve a small amount, apply a migration haircut, or be zero. The child resumes with the collateral actually migrated and raised. See the Truth Auction design for timing, caps, rounding, bidding, and settlement. See the collateral-repair illustrations Parent settlement collateral 50 ETH Migration-routed collateral 47.5 ETH Auction ETH raised 2.5 ETH A child pool before auction, after full auction repair, and after weak auction demand settles at the collateral actually raised. Truth Auction Balance Sheet The auction compares received settlement collateral with the parent settlement target and shows any remaining shortfall. Child collateral repair progress Collateral Repair Progress Blue shows settlement collateral routed during migration, green shows auction repair, and the dashed line marks the parent settlement target. Migration-routed collateral Auction repair Dashed line: parent settlement target Routed collateral 47.50 ETH Initial shortfall 2.50 ETH Remaining shortfall 0.00 ETH Repair status no contribution required","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"oracle","heading":"9. REP/ETH Price Oracle","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast uses a REP/ETH price to evaluate pool security. The price feed supports operations whose safety depends on REP value relative to pool settlement collateral.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"statoblast-glossary","heading":"Glossary","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Capacity ownership A REP-denominated, price-independent share of pool minting capacity and fee eligibility. Each deposit creates incremental ownership from its own deposit target factor; a vault has no authoritative aggregate target. The live oracle quote converts ownership into ETH minting capacity; it is not the vault's ETH debt. Settlement collateral ETH held by a security pool to redeem complete sets and settle the winning outcome. Statoblast retrieves this price using OpenOracle ; see the OpenOracle integration explanation for the coordinator lifecycle. A REP withdrawal or liquidation executes immediately with a fresh price or stages behind an OpenOracle report. An accepted settlement callback attempts up to four still-active pending operations, consuming each success or terminal failure and skipping already-consumed entries. A rejected report leaves the price cache unchanged and terminally consumes its attached batch. Overflow operations remain active for later execution with a fresh price. REP/ETH Oracle Flow The coordinator is a queue plus cache. A fresh price can execute immediately. An accepted settlement callback attempts up to four still-active pending operations; a rejected report instead terminally consumes its attached batch without refreshing the cache. Overflow operations remain active for later execution with a fresh price.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["Zoltar","universes","questions","REP"],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Draft Zoltar is a generalized branching oracle and coordination layer for binary, categorical, and scalar questions. Rather than forcing a single answer when disagreement persists, it represents unresolved outcomes as explicit child universes. After a fork, REP holders can convert parent REP into a migration balance and use that balance to mint REP in one or more child universes. The design closely follows the Colored Coins model: the protocol branches state first, while applications and their users later coordinate which branch ultimately carries economic value. Zoltar is not itself a prediction market or an outcome-reporting oracle. Instead, it provides the shared infrastructure for questions, REP tokens, and universe forks. Any application can build on top of it and define its own local dispute process. If disagreement cannot be resolved locally, anyone able to commit the required REP threshold may fork an unforked universe using an eligible ended global question. The resulting child universes become the new foundations from which applications may choose to continue.","title":"Zoltar and branching truth","topic":"Protocol","weight":1},{"fragment":"timeline","heading":"Lifecycle Timeline","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar's lifecycle has six conceptual steps, from question registration through post-fork coordination. 1. Register question ZoltarQuestionData stores the question and defines its valid answer space. 2. A fork is triggered After the question end date, any address able to supply the threshold REP can call forkUniverse . Zoltar does not verify that a disagreement exists; applications decide when a fork is justified. 3. Parent universe forks Zoltar records the fork question and deterministically defines every valid child branch. 4. Child universes deploy lazily Branches exist by deterministic id first; contracts are deployed only when needed. 5. REP holders split Migration balances mint child REP into one or more selected branches. 6. Applications and users choose where to continue The protocol does not pick a winner. Users and applications decide where durable activity continues.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar has one responsibility: define and account for branching universes. It does not implement the market, collateral, or underwriting system built on top of it elsewhere in the repo. Its role is narrower and more fundamental: register questions encode valid answer spaces represent forks as child universes mint and burn child-universe REP turn migration balances into selected child REP In practice, an application can register a question, trigger a fork after that question ends, and use the child-universe structure that Zoltar defines. ZoltarQuestionData stores question state and answer encoding. Only Zoltar can mint the child-universe ReputationToken ; genesis REP is separately deployed and supplied to Zoltar. Zoltar can never mint more REP into a single universe than its parent has REP.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"Zoltar Contracts","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Zoltar : universe forks and REP splitting ZoltarQuestionData : question registry and outcome encoding ReputationToken : child-universe REP minted and burned by Zoltar ScalarOutcomes : scalar formatting and interpolation logic","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"universe-model","heading":"2. Universe Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# A universe is one branch of protocol state. The Zoltar.Universe struct stores the minimum data needed to identify that branch and connect it to its parent: forkTime : when the universe forked forkQuestionId : the question id recorded by forkUniverse and used to define child branches forkingOutcomeIndex : the outcome index represented by the universe when it is a child reputationToken : the REP token used inside that universe parentUniverseId : the parent branch Universe 0 is the genesis universe. Its network-specific REP token is supplied to the Zoltar constructor. Mainnet uses the existing REP deployment; Sepolia deterministically deploys a genesis REP token from the configured initial-holder allocations before deploying Zoltar . Child universes are identified deterministically as: childUniverseId = uint248 ( keccak256 ( abi.encode ( parentUniverseId , outcomeIndex ) ) ) Child universe ids are deterministic hashes of the parent universe and outcome index. Deterministic child ids make each branch reproducible from parent universe and outcome index alone. Child universes are deployed lazily when a forked branch is actually needed. Concrete balance example. Alice converts 100 parent-universe REP into a migration balance and mints 100 REP in the Yes child and 100 REP in the No child. This reproduces the parent REP claim across explicitly selected branches. Genesis ├── Yes: Alice holds 100 Yes-REP │ ├── Option A: Alice may later mint 100 Yes/A-REP │ └── Option B: Alice may later mint 100 Yes/B-REP └── No: Alice holds 100 No-REP Universe Tree Universe tree. A fork creates the full valid branch set; no branch is privileged by the contract. Forking parent Invalid branch Valid outcome branches Universe tree A fork creates the full valid branch set; no branch is privileged by the contract. Important distinction Zoltar defines the branch set. It does not select one canonical child universe or delete the others.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"global-question-scope","heading":"Global Question Scope","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Questions are global protocol objects in Zoltar. forkUniverse checks that the target universe exists, still has supply, has not already forked, and that the supplied question exists and has ended. It does not require the question to have been created for that universe. Applications that need a stricter universe/question relationship enforce it above Zoltar. Accepted eligibility boundary Zoltar intentionally accepts any existing ended global question. It does not require a minimum creation age, prior universe registration, or proof that the fork's disruption is smaller than its economic benefit. The fork-threshold REP commitment and configured haircut are the protocol's admission mechanism. A stricter relevance, aging, or cost-benefit rule is not a Zoltar invariant.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"3. Fork Thresholds and REP Economics","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# To trigger a Zoltar fork, the caller must commit the forkThresholdAttoRep : the parent universe's theoretical REP supply divided by the configured threshold divisor, rounded down. The REP threshold and haircut are the intended admission cost for a fork, including unnecessary forks that impose migration work on applications using that universe. Both the threshold and the haircut are intended to make unnecessary forks economically costly. Forking affects applications and users relying on that parent universe; each application chooses which child branch, if any, to continue using. forkThresholdAttoRep = ⌊ totalTheoreticalRepSupplyAttoRep forkThresholdDivisor ⌋ With the default divisor of 20 , this is approximately 5% of theoretical REP supply, subject to integer flooring. Triggering a fork removes the full fork threshold from the parent universe. `forkUniverse` removes `forkThresholdAttoRep` of parent REP and reduces the parent universe's theoretical REP supply by the same amount. Only part of that removed REP is credited toward REP in the child universe. forkBurnDivisor determines the haircut. With forkBurnDivisor = 5 : 20% is burnt: ⌊forkThresholdAttoRep / 5⌋ 80% becomes migration credit: ⌈4 × forkThresholdAttoRep / 5⌉ The constructor rejects forkBurnDivisor < 5 uncreditedForkHaircutAttoRep = ⌊ forkThresholdAttoRep forkBurnDivisor ⌋ forkInitiatorMigrationBalanceAttoRep = forkThresholdAttoRep - uncreditedForkHaircutAttoRep Threshold Deposit Split Threshold Deposit Split. The full parent-REP threshold leaves parent supply. Most is re-expressed as migratable child-REP credit; the haircut is not credited. Genesis REP cannot be burned natively, so the contract transfers it to the configured burn address. Child-universe REP is minted and burned directly by ReputationToken under Zoltar’s control. A child's theoretical supply is a maximum for REP that can be minted in that branch. It starts from the parent's pre-fork theoretical supply and subtracts only the uncredited haircut. Later REP added to a migration balance converts 1:1; only fork initiation pays this admission cost. A holder can voluntarily burn REP and reduce theoretical supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"Repeated-fork threshold decay","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Along one lineage, each fork subtracts its floored haircut from child theoretical supply. With threshold divisor 20 and burn divisor 5 , supply is close to 99% of the previous generation, subject to Solidity integer flooring. Repeated-fork economics. Each default fork leaves approximately 99% of the previous generation's theoretical REP supply, and the next fork threshold remains 5% of that declining supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"splitting","heading":"4. Child Universes and REP Splitting","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Once a universe forks, child universes can be deployed lazily through deployChild . A user can also add more REP into the migration balance with addRepToMigrationBalance . The core post-fork action is splitMigrationRep , which lets a holder mint child-universe REP for valid outcome indices. Supplying no outcome indices is accepted as a no-op at the Zoltar layer. For categorical questions, Invalid and any in-range categorical outcome are allowed, while out-of-range values are rejected. For scalar questions, only well-formed scalar encodings are allowed. The fork question defines the child-branch shape for the whole universe. A child branch may therefore be keyed by Invalid , a categorical outcome index, or a scalar encoding depending on which parent-universe question forkUniverse used for the fork. Colored Coins-style property The same migration balance can mint child REP in each selected child. Later value concentration determines which branch or branches matters economically. At the implementation level, splitMigrationRep validates each selected outcome against the parent universe's fork question, lazily deploys any missing child universe, and records how much of the caller's migration balance has already been minted into each child. A caller cannot mint more into one child than the source migration balance available to that caller, but the same source balance can be reproduced into multiple valid children.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"security","heading":"5. Assumptions and Security Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar is a Colored Coins -style system, so its security argument depends on user behavior and value concentration rather than on the contract being able to identify one objectively correct branch onchain. users can choose which child universe (or child universes) to continue using after a fork users prefer to continue in the universes they regard as truthful the protocol itself does not know which universe is truthful and treats all valid branches symmetrically most durable economic activity concentrates in the branch that users expect other users to keep using dishonest or abandoned branches may continue to exist, but are assumed to retain little long-term value compared with the branch that market participants keep coordinating around","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"6. Questions and Outcome Encoding","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# ZoltarQuestionData.QuestionData stores title, description, start and end time, scalar metadata such as numTicks , displayValueMin , displayValueMax , and answerUnit . Question ids are deterministic hashes of the question data. For categorical questions, that hash path also includes the sorted categorical outcome options. For scalar questions, there are no categorical labels to include, so the id is determined from the scalar question fields alone. Every call to createQuestion also requires endTime >= startTime . The contract rejects any question whose end time is earlier than its start time before it reaches scalar or categorical validation.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Categorical Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Categorical questions store sorted outcome labels. The implementation requires labels to be non-empty and strictly ordered by hash: each label's keccak256(abi.encode(label)) value must be lower than the previous label's hash, so callers provide labels in descending hash order. The contract stores the labels in outcomeLabels[questionId] . Any number of categorical outcomes can exist at the Zoltar level as long as those conditions hold.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Scalar Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Scalar questions store no categorical labels. Instead, numTicks , displayValueMin , displayValueMax , and answerUnit define the answer space. At creation time, scalar questions must satisfy numTicks > 0 and displayValueMax > displayValueMin . Onchain, each scalar answer is encoded into a single uint256 that packs: the highest bit as an invalid flag a 120-bit first payout numerator a 120-bit second payout numerator Packed Scalar Answer Packed Scalar Answer. A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks. Namespace bit First payout Second payout Packed Scalar Answer A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks . The all-zero encoding is the canonical Invalid answer for scalar questions. For a valid scalar answer, the two payout numerators must sum exactly to numTicks : firstPayoutNumerator + secondPayoutNumerator = numTicks A valid scalar answer must allocate all ticks across the two payout numerators. The all-zero word is the only canonical scalar Invalid value. Any other word with the highest bit clear is malformed, and any word with a nonzero reserved bit in 254...240 is malformed even if the payout fields would otherwise decode cleanly. At the helper and UI level, a scalar tick index named tickIndex is encoded as: firstPayoutNumerator = numTicks - tickIndex secondPayoutNumerator = tickIndex highest bit = 1 ( numTicks - tickIndex , tickIndex ) The helper encodes a tick as left and right payout numerators. ScalarOutcomes interprets secondPayoutNumerator as the position along the scalar range: displayedAtomic = displayValueMin + ⌊ secondPayoutNumerator ⋅ ( displayValueMax - displayValueMin ) numTicks ⌋ The contract interpolates the scalar answer with mulDiv , so uneven divisions round down before the value is formatted. displayValueMin and displayValueMax are stored as 18-decimal fixed-point display bounds. Formatting divides the atomic result by 1e18 and trims trailing zeroes. For example, with displayValueMin = 0 , displayValueMax = 10e18 , numTicks = 6 , and secondPayoutNumerator = 1 , the atomic value is ⌊10e18 / 6⌋ , which displays as 1.666666666666666666 rather than an unrounded real number.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"question-types","heading":"7. Question Types Supported by Zoltar","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# At the Zoltar layer, market type support comes from how questions and outcomes are encoded in ZoltarQuestionData . categorical questions, implemented as an ordered array of non-empty outcome labels binary questions, implemented as categorical questions scalar questions, implemented as a tick-based numeric range with no categorical labels","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"invalid","heading":"8. Invalid vs Malformed","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar distinguishes Invalid answers from Malformed answers. Malformed answers are rejected, while Invalid remains a valid branch and a valid final outcome. Term Meaning Effect Invalid A legitimate resolution state. Can be a valid branch and final outcome. Malformed A submitted outcome index or scalar encoding that does not fit the question’s answer space. Rejected during child-universe REP splitting and fork-aware asset branching.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["contracts","architecture","calls","assets"],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Documentation index Mental model: actors enter through pools, oracle coordination, Zoltar universes, and child repair; custody boundaries explain the call graph. This map connects the primary deployed Zoltar registry and Statoblast market contracts. It emphasizes contract-to-contract calls and asset movement, not every public user entrypoint. Factory-only deployment workers, delegate modules, event emitters, and compatibility contracts are summarized separately so the runtime path stays legible. Related reading: the generated transaction reference for exact caller and prerequisite rules, the Statoblast whitepaper for lifecycle context, and the invariant catalog for cross-contract safety properties.","title":"Contract architecture","topic":"Architecture","weight":1},{"fragment":"conceptual-model","heading":"Conceptual Model Before the Graph","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Statoblast separates identity, market accounting, price-sensitive execution, dispute escrow, and fork repair: ZoltarQuestionData stores question state and answer encoding; Zoltar manages universes, forks, and REP branches. A security pool tracks market collateral, vault accounting, open interest, and lifecycle state. The share token tracks outcome-share balances. The price coordinator stages operations that require a fresh REP/ETH price. The escalation game escrows reporting REP and tracks local dispute state. The pool forker creates and repairs child pools after a fork. One complete-set path is: a trader calls SecurityPool , the pool accepts ETH collateral, and the pool calls ShareToken to mint one share for every outcome. The event stream records the supply and accounting changes. The detailed graph below shows primary protocol interactions; supporting factories, delegates, emitters, callbacks, and compatibility contracts are summarized rather than expanded.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"overview","heading":"Primary Interaction Flow","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Arrows point from the contract initiating an interaction to the contract receiving it. The map repeats a contract when it participates in more than one phase, separating construction-time wiring, recurring runtime calls, and fork repair into three readable panels. Users and keepers enter through several of these contracts, but are omitted so the plot can focus on contract-to-contract boundaries. Contract interactions are separated into three phases. During deployment, the pool factory validates question and universe data before deploying the pool, share token, and price coordinator. During runtime, the pool manages claims and escalation escrow while guarded actions travel through the price coordinator and OpenOracle. During a fork, the share token enters the pool forker, which snapshots escalation, migrates REP through a proxy, creates and migrates child pools, and repairs backing through the truth auction. Contract Interaction Map Each panel is a distinct protocol phase; repeated contracts preserve local reading order and prevent unrelated lifecycle edges from crossing. Blue nodes are registries or oracle infrastructure, green nodes hold market state and claims, red marks local resolution, and gold marks deployment or fork coordination. Short edge labels state the action; the table below supplies its exact contract-level meaning.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"edges","heading":"What Each Arrow Means","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Initiator Receiver Phase Interaction SecurityPoolFactory ZoltarQuestionData Deployment Confirms that the question exists and is the required Yes/No categorical shape before reserving the canonical pool identity. SecurityPoolFactory Zoltar Deployment Checks the universe and resolves its REP token for the new pool and price coordinator. SecurityPoolFactory SecurityPool Deployment Deploys and records the canonical origin pool. Forker-requested child deployment copies the lineage and fork state. SecurityPoolFactory ShareToken Deployment Deploys the origin lineage token and authorizes the canonical pool; child pools reuse that same token. SecurityPoolFactory OpenOraclePriceCoordinator Deployment Deploys the REP/WETH price coordinator and wires it to the newly created pool. Zoltar ReputationToken Universe lifecycle Deploys child-universe REP, sets its theoretical supply, and exclusively mints or burns that child REP during migration and voluntary burns. SecurityPool ShareToken Market runtime Mints complete sets, burns complete sets or winning shares, and authorizes canonical child pools to continue the same lineage claims. SecurityPool EscalationGame Resolution Deploys the local game on first use and transfers vault REP into outcome-specific escrow. Settlement returns winning value or carries proofs across a fork. SecurityPool OpenOraclePriceCoordinator Risk operations Reads the coordinator's last valid REP/ETH price. Vault owners and liquidation operators stage withdrawals and liquidations directly on the coordinator before restricted pool execution; target-health deposits create capacity ownership directly at the pool. OpenOraclePriceCoordinator OpenOracle Price discovery Funds a REP/WETH report and later withdraws the settled reporter balances that it validates and returns to the report sponsor. OpenOracle OpenOraclePriceCoordinator Price settlement Calls openOracleCallback after settlement so the coordinator can validate the final report, update its cached price, and attempt bounded staged-operation execution. OpenOraclePriceCoordinator SecurityPool Risk execution After price and snapshot checks, calls the pool-only liquidation or REP-withdrawal entrypoint. Those operations update capacity ownership internally when REP backing or liquidation ownership moves. ShareToken SecurityPoolForker Share migration Initiates a source-pool fork when needed and asks the forker to create a missing canonical child before migrating a holder's shares. SecurityPoolForker EscalationGame Fork snapshot Reads carry peaks, roots, outcome balances, timing, and fork parameters so unresolved escalation state can continue in child pools. SecurityPoolForker SecurityPoolMigrationProxy Fork migration Deploys and controls the pool-specific adapter, transfers parent REP to it, and instructs it to lock, fork, split, and sweep child REP. SecurityPoolMigrationProxy Zoltar Fork migration Acts as the literal Zoltar caller whose stable address holds the migration ledger balance while parent REP is locked and split into child-universe REP. SecurityPoolForker SecurityPoolFactory Fork migration Requests canonical child-pool deployment for selected child universes. SecurityPoolForker SecurityPool Fork migration Copies or recomputes financial state, migrates vaults and collateral, and activates the selected child lifecycle. SecurityPoolForker UniformPriceDualCapBatchAuction Backing repair Starts, finalizes, and settles the child truth auction when migrated REP and inherited ETH collateral do not arrive in the required proportions.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"paths","heading":"Four Useful Reading Paths","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Deploy a market: ZoltarQuestionData and Zoltar provide the question and universe context; SecurityPoolFactory creates the pool, share token, and price coordinator. Trade and resolve locally: SecurityPool accepts ETH collateral and mints one share for every outcome through ShareToken , then escrows reporting REP in EscalationGame . Reprice and liquidate: OpenOraclePriceCoordinator obtains a report from OpenOracle , then calls a restricted execution method on SecurityPool . Continue after a fork: SecurityPoolForker migrates REP through Zoltar , asks SecurityPoolFactory for child pools, transfers the inherited state, and uses UniformPriceDualCapBatchAuction when backing must be repaired.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"supporting-contracts","heading":"Supporting Contracts Not Expanded in the Plot","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The diagram groups construction helpers and delegate modules behind the deployed component whose behavior they implement: ShareTokenFactory , EscalationGameFactory , PriceOracleManagerAndOperatorQueuerFactory , UniformPriceDualCapBatchAuctionFactory , and SecurityPoolDeployer construct the displayed runtime contracts. EscalationGameDepositDelegate , EscalationGameClaimDelegate , and the SecurityPoolForker* delegate modules execute against their owning contract's storage; they are implementation boundaries, not independent user-facing protocol hubs. SecurityPoolMigrationProxy isolates a pool's Zoltar migration balance, while SecurityPoolEventEmitter preserves pool-address event attribution under delegated fork work. WETH9 , Multicall3 , and the imported OpenOracle implementation are compatibility or external boundaries; only OpenOracle's protocol-facing relationship is shown.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"sources","heading":"Source Contracts","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"ZoltarQuestionData.sol Zoltar.sol and ReputationToken.sol SecurityPoolFactory.sol SecurityPool.sol and ShareToken.sol EscalationGame.sol and SecurityPoolForker.sol OpenOraclePriceCoordinator.sol and OpenOracle.sol UniformPriceDualCapBatchAuction.sol","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"actors-and-boundaries","heading":"Actors, custody, and one operation","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The protocol consists of several participants with distinct responsibilities. Market creators configure security pools, traders mint and hold complete sets and outcome shares, vault owners provide REP backing, liquidators repair under-collateralized vaults, auction bidders recapitalize child pools after forks, keepers execute permissionless maintenance operations, and indexers reconstruct protocol state from emitted events. Each contract handles a specific part of the protocol. Security pools hold user funds and manage market state. The Oracle Coordinator runs price-sensitive operations that need a REP/ETH price. Zoltar manages universes, questions, REP, and forks. An operation may involve several contracts, but that does not move every asset or piece of state between them. For example, when a trader mints a complete set, they send ETH to the security pool. The pool accepts the deposit, mints one outcome share for every possible outcome, and updates its accounting. Each contract changes only the state it manages, while emitted events let external indexers reconstruct the operation. The deployment graph above illustrates the primary contract interactions in this release. Supporting factories, delegates, callbacks, emitters, and compatibility contracts are omitted for clarity. Contract addresses and call relationships are deployment details, while the custody and responsibility boundaries described above remain stable across deployments.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["EscalationGame","modules","authority","accounting"],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The Escalation Game's deposit, timing, outcome-selection, and continuation rules fit into the wider prediction-market lifecycle described in the Statoblast white paper. When a Statoblast market ends, the security pool sends a valid deposit through its escalation-deposit delegate. The deposit must pass the pool's vault-backing and lifecycle checks and usually meet the configured starting bond. A smaller deposit is allowed only when it fills an outcome to the non-decision threshold . The first deposit does not resolve the market by itself. A deposit moves the local deadline only when it raises the median of the three outcome balances. A strict leader identifies the local result, but the pool can finalize it only after the deadline. If competing outcomes reach the non-decision threshold, the pool follows the fork path. Lifecycle: tentative claim → conflicting deposit → accepted amount and new deadline → local settlement or non-decision → child continuation after a fork → proof-backed claims.","title":"Escalation game architecture","topic":"Architecture","weight":1},{"fragment":"escalation","heading":"Escalation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"A vault owner with enough backing can add REP to an outcome with a valid deposit that meets the configured start bond. The deposit adds to that outcome's cumulative balance; it does not have to exceed the leader. The deadline moves only when the deposit raises the median outcome balance. Separately, the cumulative binding-capital threshold rises over time: Attrition cost is zero on days 0–2, equals the start bond on day 3, and reaches the non-decision threshold on day 52. Required support threshold A(d) = 0 for 0 ≤ d < 3; S for d = 3; S × exp(ln(T / S) × (d − 3) / 49) for 3 < d < 52; T for d ≥ 52 Here S is the configured start bond and T is the non-decision threshold. The rendered chart uses the contract's fixed-point arithmetic; this exponential expression is its readable idealization. The contract waits three days after start() before the escalation clock begins. The chart labels day 0 (game start), day 3 (activation), and day 52 (the end of the seven-week escalation interval). Try a simplified escalation round This simulator uses the contract model: a three-day activation delay, a 49-day escalation interval, and the fixed-point attrition curve. Change the configuration, then choose an outcome and requested deposit to see clipping, tie adjustment, acceptance, or rejection. Start bond 1 REP Non-decision threshold 10 REP Current Yes balance 1 REP Current No balance 1 REP Current Invalid balance 0 REP Deposit outcome Yes No Invalid Requested deposit 1 REP Days since start 0 days Configured start bond 1 REP Configured threshold 10 REP Leading outcome Yes / No Median balance 1 REP Deposit before → after Yes 1 / 2 REP; No 1 / 1 REP; Invalid 0 / 0 REP Deposit result accepted: 1 REP Activation 3 days Scheduled deadline (before → after) day 3.0 → day 3.0 Contract-model state deadline pending","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"The game end","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The game can end two ways The required escalation game increased high enough that only one side was able to keep up with it: That side wins and Statoblast finalizes on that outcome Two sides have reached the maximal deposit amount: Statoblast triggers a fork After the game ends, the payout uses binding capital , the safety boundary , and the reward-eligible cap . A winning deposit returns its principal and may receive a bonus. Any excess above the reward cap returns principal without a bonus. The game burns the winning-deposit haircut ; forked payouts can also be scaled by the fork threshold. Exact example. Let binding capital be 10 REP , so the reward-eligible cap is 10 + 10 / 2 = 15 REP . The reward pool is 10 × 3 / 5 = 6 REP , and the haircut pool is 10 × 2 / 5 = 4 REP . If the winning outcome has 15 REP and a deposit contributes 5 REP within the eligible range: Principal returned: 5 REP . Bonus: 5 × 6 / 15 = 2 REP . Winning payout: 5 + 2 = 7 REP . Haircut burned: 5 × 4 / 15 = 1.333333333333333333 REP at attoREP precision. At local settlement, a deposit position above 15 REP returns its principal but receives no bonus. If the fork threshold is 8 REP instead of the 10 REP non-decision threshold, the whole local withdrawal is scaled: 7 × 8 / 10 = 5.6 REP , rounded down to attoREP. Fork scaling can therefore reduce the final transfer below principal.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"glossary","heading":"Glossary","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"Binding capital The median REP balance across Invalid, Yes, and No. It is the amount the winning result must support and the value that sets the scheduled deadline. Non-decision threshold The configured REP balance at which two outcomes have enough support to stop local resolution and use the fork path. Safety boundary The interval above binding capital where a winning deposit can still earn a bonus. In this model it is (binding capital, reward-eligible cap] . Reward-eligible cap Binding capital plus half of binding capital. Only the portion of winning deposits up to this cap participates in the bonus calculation. Principal The REP originally deposited. A winning deposit returns its principal before any bonus is added. Haircut The REP amount calculated for burning from the reward-eligible portion of a winning deposit. It is separate from the payout amount.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"Escalation game continuation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"In AugurV2, when a fork happens, it cancels all currently running escalation games, and the players are refunded. This opens an attack vector for the oracle, discussed more in Zoltar Forks Should Not Cancel Escalation Games . The fix to the issue is to fork escalation games. This is called continuation. Escalation game continuation uses Merkle Mountain Range carry proofs and nullifier roots to migrate user game deposits, without requiring expensive data copying efforts onchain.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["liquidation","capacity","operator","receiver vault","approval","reservation","bad debt"],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Liquidation moves live ETH-denominated open interest and the corresponding REP-denominated capacity ownership away from an unhealthy vault. The receiver accepts the liability and receives the funded REP award. A separate operator may submit the transaction and pay its gas and oracle costs.","title":"Capacity and delegated liquidation","topic":"Economics","weight":1},{"fragment":"position-model","heading":"Three Distinct Roles","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The operator requests and stages the liquidation, paying that transaction's gas and any oracle cost. The receiver vault accepts moved security-bond debt and receives the capacity ownership and REP award. The target vault is the unhealthy vault whose position is reduced. If the liquidation remains staged, any account may execute it later and pay the execution transaction's gas. That permissionless executor does not replace the staged operator named by a delegated approval. The receiver and target must differ. The operator may be either one, but identity alone is not a health or Sybil-resistance boundary. For the backward-compatible path, omitting a delegated receiver makes the operator the receiver and requires no signed approval. The operator receives no liquidation ownership or REP merely for submitting the transaction. The first release assumes the operator and receiver coordinate compensation externally; the protocol neither creates a keeper marketplace nor takes a fee from the award.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"capacity-and-health","heading":"Capacity and Live Health","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A vault deposit creates incremental REP-denominated capacity ownership from the deposited REP and that deposit's selected target factor. Deposits with different targets combine, so the latest deposit target is not a vault-wide health factor. The pool totals capacity ownership in constant time. The live REP/ETH oracle price and the pool Statoblast security multiplier convert that total into current ETH minting capacity. A REP price change therefore changes capacity without rewriting vaults, and it never deletes already-open interest. Vault health uses the vault's live REP backing, locally attributed dispute-staked REP, proportional live open interest, capacity ownership, the settled REP/ETH price, and the pool multiplier. The ordinary associated-REP branch counts pool-held and locally staked REP. The free-REP branch counts only pool-held REP because locked claims may lose. Required REP is rounded upward. A delegated approval may require a higher post-liquidation health factor. 10,000 means exactly the protocol minimum. The signed factor is applied to both backing branches and is checked again against live post-liquidation state at execution; a queue-time preview is never a guarantee.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"punitive-liquidation","heading":"Funded Transfer and Bad Debt","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The requested amount is ETH-denominated debt. Execution first caps a nominal quote by the target's live open interest and by the debt whose complete 5% REP bonus the target can fund. Capacity ownership is selected from that quote and rounded downward. The debt actually moved is then the exact increase in the receiver's live, upward-rounded open-interest allocation, which cannot exceed the nominal quote. On a delegated route, the coordinator additionally requires it not to exceed the staged approval reservation; the self-receiving path has no approval reservation. A positive quote that produces no receiver debt reverts. The target and receiver fee positions are checkpointed before their ownership changes; the operator is not checkpointed unless it is also one of those vaults. Delegated Liquidation Transfer Floor-rounded proportional capacity ownership and ceiling-rounded REP backing units leave the target. The authorized receiver incurs the exact reported debt increase and receives the ownership and backing units; target claims, fees, surplus, and unmatched ownership remain. Only a full-target request records residual target debt as bad debt. Liquidation accounting The operator submits the transaction, while the authorized receiver accepts the funded liability and receives the award. grossRepAwardAttoRep = ⌈ debtMoved ⋅ repPerEthPrice ⋅ 10,500 / ( PRICE_PRECISION ⋅ 10,000 ) ⌉ The gross award is converted into REP backing units with upward rounding. Because backing units are a proportional claim on the pool's current REP balance, the credited units can convert back to more current REP than grossRepAwardAttoRep ; the excess is less than the current REP value of one backing unit. On a full-target request, target-local bad debt is the target's live debt minus the receiver's exact settled increase. The residual may include debt excluded by the REP-award funding cap and a small amount left by integer ownership and open-interest allocation rounding. It is not an ETH collateral-shortfall test. Capacity ownership is conserved between the target and receiver; any ownership not transferred remains with the target. After a funded transfer, the receiver's resulting debt must meet the configured debt minimum and its REP backing must meet the configured vault REP minimum. A transfer smaller than the debt floor can still succeed when the receiver's existing position keeps its resulting debt above the floor. The target's resulting debt must be zero or meet the debt floor; when target debt remains, its REP backing must also meet the vault REP floor. These receiver or target dust checks revert instead of converting otherwise funded debt into bad debt.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"receiver-approvals","heading":"Bounded Receiver Approval","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A delegated route needs a pool-specific approval for the exact receiver and operator. It may name one target or use the zero-address wildcard for any target in that pool. Each approval defines a cumulative ETH debt limit, a per-liquidation limit, a minimum post-liquidation health factor, an activation time, an expiration time, and a nonce. The receiver may install an approval directly or sign EIP-712 typed data. The domain binds the stable name and version, chain ID, and approval registry; the typed message explicitly binds the security pool. EOA signatures use ECDSA and contract-wallet signatures use ERC-1271. A contract-wallet signature is validated when installed and becomes explicit onchain state rather than an opaque signature retained until execution. Each receiver-scoped nonce can install only one approval, and replaying it is rejected. The receiver can revoke unused approval capacity or advance its nonce floor to invalidate older nonces. Both actions prevent new reservations without cancelling reservations already attached to staged operations. Approval state exposes available, pending reserved, and permanently consumed amounts so indexers can reconstruct every transition.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"queue-semantics","heading":"Reservation and Queued Execution","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The coordinator reserves approval quota when it stages a liquidation. The reservation is no greater than the requested debt, the snapshotted target debt, the approval's remaining cumulative quota, or its per-operation limit. The approval must remain valid through the operation's latest possible execution time. Successful execution permanently consumes exactly the debt moved and releases unused reservation. A bad-debt-only result consumes no receiver quota. Expiration and every terminal failure release the full reservation, including stale target snapshots, target rescue, price-distance failure, inactive or forked pools, caught pool reverts, panics, unknown failures, and pending-report recovery. Cleanup is permissionless and recognizes expiration before requiring a valid oracle price. A terminal operation cannot release or consume twice. The coordinator snapshots target backing and ownership to prevent a stale quote from becoming a keeper windfall, then uses the settled price and live state at execution. Receiver state changes after staging can therefore cause a safe failure and reservation release.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"fork-behavior","heading":"Fork Isolation","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Approvals and reservations belong to one pool and its coordinator. They are not copied into child pools or universes. A receiver approving liabilities in one universe has not approved them in a child. Pending parent operations remain permissionlessly cleanable after the parent becomes unable to execute, releasing their reservations.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["fees","markets","vaults","accounting"],"path":"explanation/fees.html","sectionTitle":"Explanation","summary":"Understand fee accounting across markets, vaults, and protocol operations.","text":"Vault holders have a lot responsibilities, they need to maintain enough REP to cover their commitments for open interest holders, they need to participate in escalation games, fork migrations etc. The reward for this service is fees generated from open interest. The security pool sets a side fees for vault holders to claim when they like. Earned fees are not ever taken away from the vault holders. REP becomes valuable only when its used in the ecosystem. Complete-set collateral gradually decays into an unallocated fee reserve. Fee-eligible vaults accrue fees in proportion to their assigned capacity ownership. The retention rate falls as utilization rises, so security becomes more expensive as open interest consumes underwriting slack. Retention follows a piecewise heuristic. It starts high, declines linearly until 80% utilization, and then stays at the minimum retention rate. The intent is to charge more aggressively for security as utilization rises and underwriting slack falls. At zero utilization, the constants imply roughly a 10% yearly fee. Once utilization reaches the 80% dip, the annualized fee is roughly 50% and then stays flat because the on-chain retention rate is already pinned to its minimum. The UI annualizes the rate as annualFee = 1 - (retentionRate / PRICE_PRECISION)^SECONDS_PER_YEAR . Retention and utilization curve. The annualized fee rises from roughly 10% at zero utilization to roughly 50% at 80% utilization, then remains flat because the retention rate has reached its minimum. Piecewise retention formula The 80% rule marks the point where the minimum per-second retention rate begins to apply. Fee accrual is lazy: state-changing pool operations checkpoint the global fee index, and vault operations checkpoint each vault against that index. Fractional amounts remain in explicit remainders until they accumulate into whole attoETH, so callers do not need to iterate over every vault when time advances. Fee accrual is time-clamped. The pool extracts collateral into fees only until the question end time while the universe remains unforked; after the universe forks, the fee accumulator is instead clamped to the fork time. Retention-rate updates no-op outside Operational mode or when the recalculated rate is unchanged. While operational, utilization is tracked collateral divided by live ETH minting capacity, and that capacity uses total capacity ownership. Sold truth-auction capacity enters both total ownership and the fee denominator when the auction finalizes, so it provides minting headroom, affects utilization, and starts earning fees before the winning bidder claims it. Claiming the allocation does not change live capacity or the retention curve. A child pool begins a new fee epoch when migration and any truth auction finalize. Migrated capacity and sold auction capacity enter the fee denominator at that point. When a winning bid is claimed later, the pool assigns that ownership's fees from the finalization index through the current index to the winning vault, combining fractional carry with the vault's existing remainder. Whole fees move from the pool reserve into totalClaimableVaultFeesAttoEth ; this keeps aggregate claimable fees equal to redeemable vault balances. The totalAccruedFeesAttoEth() view adds those assigned claimable fees to the unallocated reserve when callers need to reconcile all fee-adjusted collateral. A fork permanently closes the parent's fee epoch. The pool tracks how much eligible capacity ownership still has not reconciled the final index, including sold truth-auction ownership awaiting claim. Auction claims preserve aggregate fees while assigning each winner's share. After every eligible vault syncs and every auction allocation is claimed, a subsequent permissionless vault fee checkpoint returns any whole reserve attoETH that no vault can individually redeem to complete-set collateral. Until that separate reconciliation, the reserve remains protected by totalAccruedFeesAttoEth() . REP deposits mint proportional REP backing units, settlement collateral decay increments the fee index, and vault capacity ownerships claim fees from that index. Pool Accounting REP backing units and fee accounting use separate proportional ledgers: REP backing units track vault REP backing, which is not automatically withdrawable REP, while the fee index tracks decayed ETH settlement collateral owed to vaults. attoRepToBackingUnits ( attoRepAmount ) = attoRepAmount ⋅ PRICE_PRECISION if the ledger or pool-held REP balance is empty = ⌊ attoRepAmount ⋅ totalRepBackingUnits totalPoolHeldRepBalanceAttoRep ⌋ otherwise backingUnitsToAttoRep ( repBackingUnits ) = 0 if the backing ledger is empty = ⌊ repBackingUnits ⋅ totalPoolHeldRepBalanceAttoRep totalRepBackingUnits ⌋ otherwise The bootstrap branch establishes PRICE_PRECISION backing units per attoREP. Once both totals are nonzero, each conversion floors integer division exactly as the contract does.","title":"Protocol fees","topic":"Economics","weight":1},{"fragment":"","heading":"","keywords":["truth auction","clearing","settlement","repair"],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When a pool's migration deadline has passed, if a child pool lacks the ETH needed to fully collateralize its open interest, the child may start a truth auction. The fork may have been triggered either by a direct Zoltar fork or by the Statoblast Escalation Game. Because collateral follows migration rather than being copied in full, a child pool retains its obligations while lacking enough ETH to reopen cleanly. The truth auction sells child-universe REP to raise as much repair ETH as demand supports. A truth auction does not determine which outcome is true. It sells REP belonging to one already-defined child universe to repair that child pool's collateral. The auction has an ETH raise target and a cap on how much child-universe REP it may sell. That cap depends on migrated and dispute-staked REP. Statoblast writes winning claims back into vault state rather than paying simple token transfers, so bidders buy into the child pool and its matching capacity ownership instead of merely receiving REP.","title":"Truth auction economics","topic":"Economics","weight":1},{"fragment":"lifecycle","heading":"Operational Lifecycle","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Migration deadline passes; the child may start the auction later Bidding Bidding ends and finalization occurs one week after the auction actually starts Auction Lifecycle. After migration closes, startTruthAuction either activates the child immediately when no sale is needed or opens bidding. During bidding, once demand establishes a clearing tick, bids below it may refund before finalization. Finalization later activates the child; paged calls then settle winning and partial bids plus any remaining refunds. Auction Lifecycle The start transition activates a child immediately when no sale is needed. On the repair branch, losing bids below an established clearing tick may refund during bidding; finalization activates the child, and later calls settle winning and partial bids plus remaining refunds. REP doesn't leave the system. Finalization accounts the ETH actually raised and rejects contribution-only ETH, so bidder settlement never depends on a donor.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Starting the auction","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The start call is permissionless but not automatic. It is valid strictly after the eight-week migration deadline and records the actual auction start time. That timestamp starts the one-week bidding window.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Bidders submit bids at discrete price ticks , where each tick represents a maximum price in ETH per REP that the bidder is willing to pay. Tick 0 corresponds to a price of 1 ETH/REP . Each increase of one tick multiplies the price by approximately 1.0001 , while each decrease divides it by the same factor. The price represented by tick tickIndex is therefore approximately 1.0001 tickIndex ETH/REP . Uniswap uses a similar tick system. Losing bids can be refunded before finalization once current demand is enough to find a clearing tick. Only ticks below that clearing tick can be withdrawn through the pre-finalization refund path; binding or potentially winning bids stay in the auction. Once a clearing tick is found, it cannot be lowered, so currently losing bids remain losing bids.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding end","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When bidding ends, anyone may finalize the auction. Finalization clears the batch auction, sends the raised ETH into the respective security pool, and computes the accounting rates used to settle bidders. The child activates with legitimate migration settlement collateral plus retained bid ETH, even when that total is below the original fork snapshot. Full repair is not guaranteed, and the remaining impairment is borne by the affected child's open-interest holders. At finalization, unclaimed auction backing, capacity, and bad debt enter an explicit, nonwithdrawable unassigned position. When the auction purchases REP, the position also owns fees from finalization onward. getUnassignedPosition exposes its backing, capacity, and debt even when the auction purchases no REP; SecurityPoolUtils.getUnassignedPositionFeeAccounting exposes its finalization fee index and any checkpointed fee entitlement. Its open interest is the ceiling-rounded capacity share of child settlement collateral minus unassigned bad debt, floored at zero. A complete-set mint must leave both the pool and this position healthy. A later winner claim transfers the bid's proportional backing units, capacity ownership, bad debt, and accrued fees from that position into child-pool vault accounting, rather than transferring REP tokens directly. Capacity ownership and bad debt are allocated from each bid's fixed cumulative ETH position, so division dust and each winner's balance are independent of claim order. The forker tracks collateral received through migration and the auction separately from the child contract's raw ETH balance. ETH forced into the child does not count toward the repair target. Finalization activates the child with migrated collateral plus accepted auction ETH and rejects nonzero finalizer contribution ETH.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"How Clearing Works","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The clearing process walks demand from the highest price downward. A bid above the clearing tick wins in full, a bid below it loses in full, and bids at the clearing tick share the last fill. In the uniform-clearing branch that last fill is First in First Out within the tick, so earlier same-tick bids are consumed before later ones. Higher ticks represent higher bid prices and therefore receive priority during auction clearing. The clearing algorithm matches bids starting from the highest tick and works downward until all available REP has been allocated. The auction has two caps ETH raise cap. The auction only wants to raise ETH required to make open interest holders whole REP selling Cap. The auction can at most sell all the rep it has As soon as cumulative demand is enough to hit either cap at some price, that price becomes the clearing tick and all winning bids settle from it at one price. That is a valid uniform clearing even when the REP cap binds before the ETH repair target is fully met.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"Underfunded Clearing","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When demand never reaches a full clearing price, the auction switches into the underfunded branch. Finalization computes underfundedThreshold , stores the lowest tick whose price reaches that qualification threshold as clearingTick . Only bids at or above the cap-implied qualification threshold are retained; lower ticks refund. Qualifying bidders collectively purchase maxRepBeingSold for underfundedWinningEth , so the common effective price is their retained ETH divided by the REP cap. That effective price can be lower than the qualification threshold; the threshold is not an execution-price floor. If no ETH qualifies, every bid refunds. Sold REP is allocated between them proportionally with integer floors: “them” means the pool-held and unresolved dispute-staked REP buckets. The dispute-staked bucket receives ⌊repPurchased * disputeStakedRepBefore / combinedRepBefore⌋ , and the pool-held bucket receives the complementary remainder. This bucket split happens before REP is allocated among qualifying bidders, and each bucket keeps separate backing and retention accounting. underfundedThreshold = ⌈ attoEthRaiseCap ⋅ PRICE_PRECISION maxAttoRepBeingSold ⌉ PRICE_PRECISION = 1e18 . The ceiling tick is the bid-eligibility boundary, not an execution-price floor. When the auction finalizes with purchased REP, the sold capacity ownership enters feeEligibleCapacityOwnershipAttoRep . It affects retention utilization and accrues fees into the unallocated reserve before a winner claims. After a zero-purchase auction, the system-owned remainder stays visible in the unassigned position but remains outside fee eligibility because no bidder has an economic fee entitlement. Bounded child-local REP backing units. Vault migration first converts each parent claim into REP, then credits that amount as child-local backing units. It never copies a recursively scaled parent unit balance. Every positive migrated amount reserves at least one attoREP of the pool's own inventory. If unresolved escalation REP is auctioned with the pool-held REP, the forker scales that reserve across the combined inventory and rounds upward. This guarantees that proportional allocation leaves enough pool-held REP—not merely game REP—to back the migrated claim after another owner redeems. Zero pool-incumbent case. If proportional allocation leaves no REP for the migrated pool owners, finalization uses the fixed PRICE_PRECISION auction rate. This includes a zero-migration mixed auction whose unsold unit belongs entirely to the escalation game. Positive pool-incumbent case. Here poolRepAfter is the REP held by the pool after the auction and escalation haircut, while incumbentRepAfter is the portion of the pre-auction pool-held REP left for migrated owners by proportional allocation. The auction rate is ⌈poolRepAfter / incumbentRepAfter⌉ . The resulting denominator is a multiple of pool-held REP, so winner backing units round-trip exactly through backingUnitsToAttoRep . Both cases use full-precision multiplication so every supported conversion remains representable.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"avl-tree","heading":"AVL tree","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The auction indexes bids in an AVL tree keyed by tick. The valid tick range is finite: [-524288, 524288] , or 1,048,577 possible price levels. The tree stores only submitted ticks and keeps each subtree's demand summary. Because a full tree over the range has maximum height 28 , finalize() follows bounded paths and prunes subtrees instead of looping over every possible price level.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"interactive-example","heading":"Interactive Clearing Example","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The slider example shows the uniform-clearing path, the underfunded path, and how the same bid ladder reacts when the ETH cap, REP cap, and bid sizes change. Try a simple auction clearing run The simplified calculator visualizes both finalized branches with three fixed price levels and real-number arithmetic. The clearing section specifies the contract's exact tick and cumulative-floor behavior. ETH raise cap 12 ETH REP inventory 4 REP Alice bid near 5 ETH/REP 3 ETH Bob bid near 4 ETH/REP 4 ETH Carol bid near 3 ETH/REP 6 ETH High-price bids are considered first. If cumulative demand reaches the ETH raise cap or REP sale cap, the first binding tick sets normal uniform clearing. If neither cap is reached, finalization selects bids at or above the cap-implied qualification tick and allocates the complete REP sale cap among them at one effective price. With no qualifying bids, every bid refunds. Interactive Demand Curve The stepped curve accumulates bids from the highest limit price to the lowest. The vertical rule is available REP, the horizontal rule is the current clearing or qualification price, and colored bid points distinguish accepted demand from demand below the boundary. The chart updates from the auction controls directly above it. Mode uniform clearing near 3 ETH/REP Binding condition both caps ETH retained 12 ETH Winning ETH kept not underfunded Threshold not underfunded Approximate Alice REP 1.00 REP Approximate Bob REP 1.33 REP Approximate Carol REP 1.67 REP Total REP allocated 4 REP Refunds 1.00 ETH","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"three-auction-outcomes","heading":"Three outcomes to keep separate","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"No auction is required when migrated REP already repairs the child pool. A funded auction means a clearing cap bound, not necessarily full ETH repair: compare accepted ETH with the repair cap. An escalation-allocation haircut can still apply when purchased REP is allocated to positive dispute-staked REP. An underfunded auction clears qualifying demand at or above the reserve threshold and refunds lower bids; it records the ETH shortfall, may apply the conditional escalation-allocation haircut, and cannot create missing ETH or duplicate REP.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["OpenOracle","price","coordinator","callback"],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle is a source for REP/ETH price for Statoblast. Open oracle supplies a fresh REP/ETH price for solvency-sensitive operations. It does not decide market truth; local escalation games and Zoltar remain responsible for truth and branching.","title":"OpenOracle integration","topic":"Architecture","weight":1},{"fragment":"openoracle-role","heading":"Why Statoblast uses OpenOracle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Statoblast needs a REP/ETH price to enforce live backing and convert price-independent REP capacity ownership into ETH minting capacity. Price-gated operations include REP withdrawals, liquidations, and certain Escalation Game deposits. A stale or easily manipulated price could allow an unsafe withdrawal or make a healthy vault appear liquidatable. A price change updates aggregate ETH capacity arithmetically and never requires a vault scan. Statoblast therefore uses OpenOracle to obtain a fresh, economically contestable REP/ETH price. OpenOracle does not prove a price by consulting a trusted source. Instead, a reporter posts a WETH/REP position, and other participants can replace an inaccurate report through an, economically motivated correction trade. Statoblast configures WETH as token1 and REP as token2 . Keeping WETH on the exact side lets the coordinator size the report directly from ETH-denominated gas costs without first assuming a REP/ETH conversion price. When a report settles, the coordinator converts the settled token amounts into REP per ETH: lastPrice = amount2 ⋅ 10 18 amount1 The settled REP amount ( amount2 ) is scaled by the fixed price precision and divided by the settled WETH amount ( amount1 ). This raw-amount ratio is valid because the configured WETH and REP contracts both use 18 decimals. The coordinator does not normalize token decimals in the settlement callback. An accepted report becomes the coordinator's cached REP/ETH price. It is reusable only while it remains inside PRICE_VALID_FOR_SECONDS , currently five minutes. The coordinator does not meter how much operation volume uses that price during the freshness window.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"statoblast-integration","heading":"Integration architecture","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Every security pool has its own OpenOraclePriceCoordinator . The coordinator connects users, the pool, and the shared OpenOracle instance. It keeps the pool's cached REP/ETH price, pending report state, staged-operation queue, and settlement callback state. Keeping this state per pool prevents one pool's pending report or freshness window from becoming a protocol-wide queue. OpenOraclePriceCoordinator is deployed with an immutable OpenOracle address. For each origin or child security pool, SecurityPoolFactory asks PriceOracleManagerAndOperatorQueuerFactory for a fresh coordinator wired to the shared OpenOracle instance, shared WETH, and that universe's REP token. SecurityPoolDeployer passes the coordinator to the pool, after which the factory calls setSecurityPool once with the nonzero pool address. Only the configured pool may seed the coordinator's inherited lastPrice . A child pool inherits the parent's numeric price as a starting value, but its settlement timestamp remains zero, so that number is unusable until the child accepts its own report. The coordinator's validity window then determines whether the child price can be used. After OpenOracle settles, the coordinator validates the report. An accepted report refreshes the REP/ETH price and runs pending operations through local guardrails. A rejected report leaves the price cache unchanged and terminally fails the attached pending batch. Integration Flow The coordinator is the trust boundary between Statoblast operations and OpenOracle reports. After settlement, an accepted report refreshes the cache before guarded execution; a rejected report leaves the cache unchanged and terminally fails the attached batch.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Price request lifecycle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A liquidation or REP withdrawal begins at the pool's coordinator. REP deposits create capacity ownership directly from that deposit's selected target factor and do not use this staged oracle path. The coordinator first validates and stages a withdrawal or liquidation, then either uses a cached price or opens an OpenOracle report. Validate and stage the operation. The coordinator checks the caller, target vault, requested amount, and validity window before any report is requested. Check the cached price. If the cached REP/ETH price is fresh, the coordinator attempts the operation immediately. Open a report when the price is stale. The refresh sponsor supplies the request bounty and initial WETH/REP position, and the coordinator creates the report as its initial reporter. Allow corrections. Other participants may replace the report by posting the position required by OpenOracle. Every valid replacement installs a new reporter and restarts the settlement window. Settle the current report. When its settlement deadline is reached without another dispute, OpenOracle settles the report and calls the coordinator. Accept or reject the price. The coordinator validates the report identity, history, economics, amounts, and settlement-time base fee before updating the cache. Execute queued operations. An accepted fresh price allows the coordinator to attempt the queued pool operations, each of which still performs its own current safety checks. The complete flow is operation requested → cached price checked → immediate execution or report opened → report corrected or left unchanged → report settled → coordinator accepts or rejects → operation safety checks → execution .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Staging rules","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Liquidation and withdrawal amounts must be nonzero. validForSeconds must be positive and no greater than five minutes. A liquidation's receiver vault must differ from its target vault. The operator may be the target; operator identity is not used as a Sybil-resistant boundary. Non-liquidation operations must target the caller's own vault. Staging is disabled after the security pool's local Escalation Game has resolved. A liquidation whose receiver equals its target is rejected with Receiver is target before the coordinator requests or consumes an oracle report. A self-receiving operator uses a zero approval ID; a distinct receiver requires a bounded approval.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Execution batches","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"If a cached price is valid, the coordinator stages the operation record and then attempts immediate execution. Liquidation gates, a stale queue-time snapshot, a zero-effect withdrawal, or a downstream pool check may still cause the staged operation to fail and be consumed. When a new report is needed, one settlement callback can automatically attempt up to MAX_PENDING_SETTLEMENT_OPERATIONS = 4 operations. Additional staged operations remain active outside the callback batch. If they have not expired and the accepted price remains fresh, they can be executed later with executeStagedOperation .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Escalation Game deposits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Escalation Game REP deposits do not use the staged callback path. When a pool has active capacity ownership, depositToEscalationGame requires an already-fresh coordinator price and reverts when the price is stale. After previewing the accepted REP deposit, the pool uses lastPrice for the post-transfer vault and pool coverage checks.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"request-funding","heading":"Funding and report ownership","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Report funding is caller supplied. The coordinator's getRequestPriceCostAttoEth() getter calculates the request bounty from the current base fee, callback gas limit, and the coordinator's own report processing budget: requestPriceCostAttoEth = block.basefee ⋅ 4 ⋅ ( callbackGasLimit + gasConsumedOpenOracleReportPrice ) + 101 callbackGasLimit is the gas reserved for the settlement callback, gasConsumedOpenOracleReportPrice is the coordinator's own report-price callback work, and the small 101 offset keeps the forwarded bounty strictly above the computed gas product so the OpenOracle-funded reward path has a positive buffer instead of landing exactly on the boundary. If a cached price is usable, no new oracle request cost is retained and unused ETH is refunded. If a report is needed, only the first pending settlement slot retains the request cost. requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) forwards exactly the bounty to OpenOracle and refunds any excess. The same transaction submits the initial WETH/REP position, so the refresh sponsor funds both the ETH bounty and report position up front. Both public request paths refund only a positive unused or excess amount by making a low-level native-ETH call to the caller. A contract caller must accept that callback. If it rejects the refund, the whole transaction reverts, including operation staging, immediate execution, and any report opened in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"rolling-dispute-exclusivity","heading":"Sponsor lane","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator refuses a direct requestPrice call while the cached price is still fresh, preventing a redundant report from being opened over a usable price. Once the price is stale and a report is pending, only the original pendingReportSponsor may add operations to that in-flight settlement. Those follow-up operations pay no additional join fee; other callers must wait until the report finishes. Every successful dispute replaces the current report and restarts its settlement window. The resulting liveness consequences are described under Economic tradeoffs and limits .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Report sizing and correction incentives","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle's correctness depends on profitable arbitrage correcting inaccurate prices. The coordinator sizes each report so that correcting an error at or above the target remains profitable after modeled dispute costs and OpenOracle fees, with the configured security margin. In the conservative correction direction, the true REP/ETH price is (1 + targetError) times the reported price. Before gas, correcting that report releases the fraction (targetError - fees) / (1 + targetError) of the WETH position. The sizing formula therefore multiplies the modeled gas cost of one dispute by the security multiplier and divides by that correction-profit fraction. The coordinator sizes the initial WETH position to make correction economically profitable when the price deviates enough. Every dispute resets the oracle's reporting clock, so repeated disputes can delay settlement indefinitely. The position therefore also accounts for pool open interest. The minimum initial WETH position combines three inputs: Request-block base fee: a report sized from the current block.basefee . Lineage priority-fee assumption: a separate report sized from the immutable initialReportPriorityFeeAttoEthPerGas . Pool open interest: a floor equal to one percent of the pool's current settlement collateral. The priority-fee report is added to the larger of the base-fee report and the open-interest report. The sponsor may use this minimum or request a larger initial WETH position. reportAttoEth ( gasPriceAttoEthPerGas ) = ⌈ gasPriceAttoEthPerGas ⋅ gasUnitsForOneDispute ⋅ openOracleSecurityMultiplierBps ⋅ ( percentagePrecision + targetPriceErrorForDispute ) 10000 ⋅ ( targetPriceErrorForDispute - protocolFee - reporterFee ) ⌉ openInterestReportAttoEth = ⌈ settlementCollateralAttoEth 100 ⌉ minimumToken1ReportAttoEth = reportAttoEth ( initialReportPriorityFeeAttoEthPerGas ) + max ( reportAttoEth ( block.basefee ) , openInterestReportAttoEth ) escalationHaltAttoEth = max ( ⌊ initialWethReportAttoEth ⋅ escalationHaltMultiplierBps 10000 ⌋ , openInterestReportAttoEth ) The initial-report minimum adds a report derived from the lineage's immutable initialReportPriorityFeeAttoEthPerGas to the larger of a report derived from the request block's block.basefee and one percent of current pool open interest. The multiplicative-escalation halt is the greater of the selected initial report times the configured halt multiplier and one percent of the pool's stored ETH collateral backing complete sets. The open-interest division rounds up. No REP price, expected REP price movement, or individual protected-operation notional enters the minimum WETH calculation. The immutable priority fee is selected when the origin pool is created and inherited unchanged by its child pools. Open interest contributes ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ , with OPEN_INTEREST_DIVIDER = 100 , before the priority-derived report is added. The coordinator sets initialWethReportAttoEth = max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) , uses that value as OpenOracle's currentAmount1 , and derives amount2 = ⌈initialWethReportAttoEth * proposedRepPerEthPrice / 1e18⌉ in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Formula inputs","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Formula input Current value Source block.basefee Current request block EVM block context initialReportPriorityFeeAttoEthPerGas Origin-pool parameter; the UI defaults to 10 gwei Immutable lineage configuration inherited by child pools settlementCollateralAttoEth Current pool open interest in ETH units Configured security pool OPEN_INTEREST_DIVIDER 100 (a 1% floor) Coordinator constant gasUnitsForOneDispute 300,000 gas ORACLE_GAS_UNITS_FOR_ONE_DISPUTE openOracleSecurityMultiplierBps 100,000 bps ( 10x ) Factory constructor; initial default OPEN_ORACLE_SECURITY_MULTIPLIER_BPS targetPriceErrorForDispute 500,000 / 10,000,000 ( 5% ) Factory constructor; initial default ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE protocolFee 1% Coordinator/OpenOracle report parameters reporterFee 0.1% Coordinator/OpenOracle report parameters","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Construction bounds and parameter effects","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Coordinator construction requires: positive dispute gas units a target error no greater than 100% an Open Oracle Security multiplier of at least 1x combined OpenOracle fees below the target error It also rejects a priority fee whose constant-derived report would consume the reserved uint128 report or escalation-halt capacity; half of that capacity remains available for the dynamic base-fee or open-interest component. The factory stores its deployment configuration, so an invalid parameter domain causes coordinator deployment to revert. Moving the target error toward the fee floor increases the required WETH sharply. Increasing the Open Oracle Security multiplier increases the required WETH linearly. A larger target error reduces the required position, but permits a wider error before the modeled correction incentive applies. The immutable priority fee is an operator-selected transaction-inclusion assumption, not a tip observed from the request block. Setting it too low weakens the modeled incentive for a disputer when prevailing priority fees are higher. Setting it too high raises the sponsor's initial WETH and REP requirements and the initial-report-derived escalation halt, and can make requests impractical. Child pools inherit the origin's value. At a 30 gwei base fee and a configured 10 gwei priority fee, the modeled gas cost is 0.012 ETH . After applying the 10x Open Oracle Security multiplier and the worst-direction five-percent correction fraction after fees, the minimum report with 100 WETH of open interest is 3.230769230769230770 WETH . With the configured 10x halt multiplier, the initial-report-derived escalation halt is 32.307692307692307700 WETH . Dynamic WETH report estimator This calculator mirrors the onchain formula. The deployment parameters and caller-selected amount are shown as adjustable inputs to make their effect explicit. For a deployed coordinator, the base fee and pool open interest can change from one request block to another. Block base fee 30 gwei Initial-report priority fee 10 gwei Pool open interest 100 WETH Gas units for one dispute 300,000 gas Open Oracle Security multiplier 10.0x Target wrong-price error 5.0% OpenOracle protocol fee 1.0% OpenOracle reporter fee 0.1% Requested initial WETH 0.00 WETH Escalation halt multiplier 10x Initial-derived halt 32.307692307692307700 WETH Open-interest halt floor 1.000000000000000000 WETH Minimum token1 report 3.230769230769230770 WETH Selected initial WETH 3.230769230769230770 WETH Escalation halt 32.307692307692307700 WETH Modeled dispute gas cost 0.012000 ETH Buffered gas target 0.120000 ETH Correction profit fraction after fees 3.7143% Safety state fees below target error","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Settlement validation, rejection, and recovery","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Low-level callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle invokes the coordinator through a low-level callback whose success is intentionally neither stored nor emitted. A failed callback does not automatically undo an otherwise valid OpenOracle settlement. The coordinator accepts callbacks only from the configured OpenOracle and only for the current pending report id. If those checks revert inside the low-level call, the report can still settle while the coordinator remains pending. If the settlement transaction cannot retain enough gas headroom after the callback attempt, OpenOracle reverts settlement with InvalidGasLimit . In that case, the report has not settled and coordinator recovery is not yet available.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"settlement-validation-scope","heading":"Coordinator settlement validation","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A callback that successfully enters the coordinator can still produce a rejected price. The coordinator clears the pending report id, emits PriceReportRejected , and leaves the price cache unchanged when any of the following conditions holds: the settlement base fee exceeds the request-time cap; storedGame(reportId).numReports is saturated at the uint24 maximum, reported as Counter saturated ; the final history record's WETH amount is too small to support another dispute at that record's base fee plus the configured priority fee, reported as Report uneconomic ; either settled token amount is zero; or the integer REP/ETH calculation produces a zero price. When dispute tracking is enabled, OpenOracle records each report block's base fee. If the report counter is not saturated, the coordinator selects history index numReports - 1 and checks the final WETH position against the configured security-sizing formula using that record's base fee plus the configured priority fee. It also enforces the request-time settlement base-fee cap before recording the final ratio. These checks establish only that the final WETH position meets the modeled security floor and that the callback data is structurally acceptable. They do not prove that the accepted price is externally correct, that an independent corrector exists, or that a correcting transaction will obtain inclusion.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Rejected-price cleanup","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A rejected report does not replay its pending settlement operations. Instead, the coordinator terminally fails every operation attached to that report, removes it from the active and pending sets, and releases any liquidation-approval reservation exactly once. A caller must stage a new operation to try again with a later price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Recovering a settled report after callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"When OpenOracle has already settled but the coordinator remains pending because the low-level callback did not complete, anyone can call recoverSettledPendingReport . Recovery clears the pending report, sponsor, and base-fee cap; withdraws coordinator-owned OpenOracle balances; and terminally fails every pending settlement operation attached to the report. Recovery does not treat those operations as successful and does not accept the report as a new price. Each affected liquidation reservation is released during the same cleanup, so no approval quota remains locked.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Economic tradeoffs and limits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"No accepted-price notional budget","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator does not maintain a notional exposure budget. One accepted price may authorize multiple otherwise-valid operations during its freshness window. The report position is therefore an economic correction incentive, not a bond sized to the exact aggregate payoff of every operation that may use the price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Unbounded funded delay","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The stale-price refresh sponsor retains the staging lane while its report is pending. A timely valid dispute replaces the reporter and restarts OpenOracle's settlement clock. Repeated disputes can therefore delay settlement. Each extension requires a transaction and the contract-specified replacement position. Under ordinary non-dust parameters, fees and required position size also accumulate. There is intentionally no absolute coordinator deadline. The protocol does not claim liveness against a participant with unbounded capital that is willing to keep funding valid replacements. This availability model is separate from the dynamic initial WETH sizing rule and is not a bounded-liveness invariant.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Dispute escalation after the halt","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"In this integration, WETH is the escalation variable. Before escalationHalt , OpenOracle requires min(⌊oldAmount1 × multiplier / 100⌋, escalationHalt) . With the configured multiplier of 115 , integer flooring leaves token1 amounts from one through six attoETH unchanged; seven attoETH is the first amount that grows. At or above the halt, each replacement instead requires oldAmount1 + 1 , adding one attoETH of WETH. The selected initial amount is max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) . One escalation-halt candidate is that amount multiplied by the configured halt multiplier. The other is ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ . The actual halt is the greater of the two, as shown in Dynamic Report and Escalation Threshold .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"attack-model","heading":"External-payoff and censorship model","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"This stress test models an attacker that posts a manipulated REP/ETH report, excludes a correcting transaction until settlement, and receives an outside payoff if the accepted price makes a liquidation executable. It is a continuous approximation for reasoning about incentives, not a contract-enforced exposure cap or a guarantee that a correction will obtain inclusion. The payoff is binary: the attacker receives externalPayoff only when the manipulated price is strictly above the liquidation threshold and far enough beyond the configured liquidation-distance guard. The cost side grows with the report's WETH-denominated liquidity, the duration of censorship, and the price error above the modeled honest-dispute barrier. honestDisputeBarrierFraction is a model input that abstracts the cost an honest corrector must overcome before the model assigns censorship pressure. It is not a coordinator field or a separately configured OpenOracle parameter. The calculator's illustrative 1.1% default is only the fixed-fee floor: the configured 1% protocol fee plus 0.1% reporter fee. A deployment analysis must replace it with a full scenario-specific barrier. OpenOracle's report-cost derivation also depends on volatility over the settlement horizon and expected loss under adversarial disputes; gas, priority fees, price impact, and capital lockup can raise the barrier further. executionErrorThreshold = max ( 0 , liquidationThresholdPrice honestPrice ⋅ ( 1 - minLiquidationPriceDistanceBps 10000 ) - 1 ) manipulatedPriceError = max ( 0 , manipulatedPrice - honestPrice honestPrice ) liquidationExecutable = manipulatedPrice > liquidationThresholdPrice ∧ manipulatedPriceError ≥ executionErrorThreshold attackerPayoff = liquidationExecutable ? externalPayoff : 0 censorshipRate = max ( 0 , manipulatedPriceError - honestDisputeBarrierFraction ) censorshipCost = censorshipDuration ⋅ censorshipRate ⋅ oracleReportLiquidity Price errors and the dispute barrier are fractions of the honest price. externalPayoff and oracleReportLiquidity use the same ETH-denominated unit. The liquidation design owns the exact integer threshold and distance checks. The derived duration threshold applies only when the manipulated price unlocks the outside payoff. It asks how many abstract model steps are required for censorship cost to reach a chosen multiple of that payoff. targetGriefRatio is the desired attacker loss after subtracting externalPayoff , divided by that payoff. A value of 1 therefore targets censorship cost equal to twice the payoff, leaving a modeled net loss equal to the payoff. One censorship step is the interval over which the chosen censorshipRate and report-liquidity cost accrue; it is not inherently a block, second, or dispute round. A deployment analysis must define that time mapping before applying the threshold. When a payoff is unlocked, the result is unbounded if the modeled censorship rate is zero; otherwise it shrinks as price-error pressure or report liquidity relative to the outside payoff increases. oracleLiquidityRatio = oracleReportLiquidity externalPayoff safeCensorshipDuration = { targetGriefRatio + 1 censorshipRate ⋅ oracleLiquidityRatio , liquidationExecutable ∧ censorshipRate > 0 ∞ , liquidationExecutable ∧ censorshipRate = 0 not applicable , otherwise safeCensorshipDuration is the model's historical name for this cost threshold; it does not establish a maximum protocol delay. Repeated funded disputes can restart the settlement window as described under Economic tradeoffs and limits . Binary censorship stress test Adjust the liquidation boundary, outside payoff, report liquidity, and censorship assumptions. The barrier starts at the fixed-fee floor and must be calibrated for the modeled deployment. Presets provide executable, guarded, and long-censorship scenarios. Honest price 100 Manipulated price 113 Liquidation threshold 101 Liquidation distance 1000 bps External payoff 1000.00 ETH Report liquidity 4000.00 ETH Honest dispute barrier (model) 1.10% Censorship duration (model steps) 24 steps Target excess-cost ratio 1.0 Execution error threshold 12.22% Manipulated price error 13.00% Liquidation executable yes Attacker payoff 1000.00 ETH Censorship cost 11424.00 ETH Oracle liquidity ratio 4.00 Duration to target cost (model steps) 4.20","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"parameters","heading":"OpenOracle parameters used","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Use the coordinator reference for source constants and parameter definitions, and deployment verification for deployed getter values. The Statoblast whitepaper links to both. Parameter Current integration value Use in Statoblast token1Address / token2Address WETH / REP Defines the REP/ETH price pair used for security-pool solvency checks. minimumToken1ReportAttoEth() Dynamic; see Dynamic Minimum WETH Report and the estimator . Adds the priority-fee report to the larger base-fee or open-interest correction floor. initialReportPriorityFeeAttoEthPerGas Configured per origin pool; UI default 10 gwei Provides an immutable inclusion-fee component that every child pool inherits. OPEN_INTEREST_DIVIDER 100 Makes one percent of settlementCollateralAttoEth the open-interest-derived initial-report and escalationHalt floor. OpenOracle initial currentAmount1 The greater of minimumToken1ReportAttoEth() and the sponsor's requestedInitialAttoWeth The sponsor may request and fund more than the minimum; the coordinator submits the selected amount as currentAmount1 . escalationHalt The greater of 10x the selected initial currentAmount1 and 1% of pool open interest Raises the multiplicative-escalation threshold with pool exposure and stops multiplicative report-size escalation there; later disputes add one attoETH of WETH at a time. settlerRewardAttoEth Dynamic; see Oracle Request Cost . The full request bounty is assigned to the account that settles the report and triggers the callback. settlementTime 480 seconds (8 minutes) Sets the timestamp-based report settlement delay. The 40 * 12 derivation assumes twelve-second blocks, but the configured OpenOracle timeType uses seconds. Staged operations expire after this delay plus their operation-specific validity window. disputeDelay 0 Allows disputes immediately after each report and strictly before its settlement deadline. At the deadline, settlement is valid and a dispute is too late. callbackGasLimit Derived from settlement gas and the maximum callback batch; see Callback Gas Limit . Sets the callback gas limit for up to MAX_PENDING_SETTLEMENT_OPERATIONS staged operations; settlement still needs enough surrounding gas to satisfy OpenOracle's callback headroom check. feePercentage / protocolFee 10000 / 100000 Configures OpenOracle reporter and protocol fee accounting. Those fees contribute to the correction friction abstracted by honestDisputeBarrierFraction in the external-payoff model . protocolFeeRecipient 0x000000000000000000000000000000000000dEaD Receives OpenOracle protocol fees for the coordinator-created report instance. multiplier 115 Before escalationHalt , requires WETH token1 equal to ⌊prior amount × 1.15⌋ , capped at the halt. Integer flooring leaves prior amounts of one through six attoETH unchanged. At or above the halt, each dispute requires one more attoETH of WETH. timeType / trackDisputes true / true Uses timestamps. Coordinator reports always enable dispute history so settlement can validate the final report's recorded base fee. Settlement rejects the price if the uint24 report counter is saturated. callbackContract OpenOraclePriceCoordinator Routes settled amounts back into Statoblast's price cache and staged-operation executor.","title":"OpenOracle integration","topic":"Architecture","weight":0}] diff --git a/docs/mainnet-deployment-addresses.json b/docs/mainnet-deployment-addresses.json index ffb836985..2fadd2eae 100644 --- a/docs/mainnet-deployment-addresses.json +++ b/docs/mainnet-deployment-addresses.json @@ -92,6 +92,26 @@ } ], "derivedContracts": [ + { + "id": "securityPoolForker", + "label": "Security Pool Forker", + "address": "0x51cf207A931e76C837441dA3b70c57d71C62fF77" + }, + { + "id": "escalationGameClaimDelegate", + "label": "Escalation Claim Checkpoint Delegate", + "address": "0x26CA07838f50Fd03d9b586f8780F42De9877286f" + }, + { + "id": "escalationGameFactory", + "label": "Escalation Game Factory", + "address": "0xEc0d2a449F6Ec70998043b18F680bC42Cf2f3249" + }, + { + "id": "securityPoolFactory", + "label": "Security Pool Factory", + "address": "0x3A7b70613E86908a4494D3cBf627C62342c96990" + }, { "id": "escalationGameProofVerifier", "label": "Escalation Game Proof Verifier", diff --git a/docs/reference/operator-guardrails.html b/docs/reference/operator-guardrails.html index 001193f1d..035cb0e28 100644 --- a/docs/reference/operator-guardrails.html +++ b/docs/reference/operator-guardrails.html @@ -561,7 +561,7 @@

REP/ETH Oracle Operations

Atomic initial report The sponsor funds the ETH bounty and WETH/REP position; the coordinator submits that position atomically as the initial reporter. A caller-selected WETH amount above the dynamic minimum is allowed. See OpenOracle sizing and funding for derivation, reporter withdrawals, application buffering, and escalation effects. - OpenOraclePriceCoordinator.sol, openOracle.ts + OpenOraclePriceCoordinator.sol, openOracle.ts Immediate execution @@ -581,7 +581,7 @@

REP/ETH Oracle Operations

Delegated receiver approvals LiquidationApprovalRegistry supports direct receiver approvals and EIP-712 permits from EOAs or ERC-1271 wallets. An approval binds pool, receiver, exact operator, exact or wildcard target, cumulative and per-operation ETH debt limits, minimum post-health factor, validity window, and nonce. Revocation blocks new reservations without disturbing pending ones; nonce invalidation blocks stale approvals. - LiquidationApprovalRegistry.sol, openOracle.ts + LiquidationApprovalRegistry.sol, openOracle.ts Approval reservation lifecycle @@ -750,6 +750,7 @@

SecurityPoolUtils read surface

+

Production contract inventory

@@ -872,6 +873,21 @@

SecurityPoolUtils read surface

+ + + + +
Shared constants and math, outcome representations, Multicall3 aggregate, tryAggregate, tryBlockAndAggregate, blockAndAggregate, aggregate3, aggregate3Value, and block-context reads, plus standard WETH9 deposit, withdraw, approval, and transfer support.
Trading protocol + PredeploymentShareSink.sol, TwoWayConstantProductFactory.sol, TwoWayConstantProductMath.sol, TwoWayConstantProductPair.sol, TwoWayConstantProductRouter.sol, ITradingShareToken.sol, ITwoWayConstantProductFactory.sol, ITwoWayConstantProductPair.sol. + Deterministic two-outcome pool deployment, constant-product pricing and reserves, routed liquidity and swap operations, canonical Statoblast share-token integration, and quarantine of shares sent to counterfactual pair addresses before deployment.
Imported oracle integration diff --git a/docs/sepolia-deployment-addresses.json b/docs/sepolia-deployment-addresses.json index 731e26029..2e6430607 100644 --- a/docs/sepolia-deployment-addresses.json +++ b/docs/sepolia-deployment-addresses.json @@ -102,6 +102,26 @@ } ], "derivedContracts": [ + { + "id": "securityPoolForker", + "label": "Security Pool Forker", + "address": "0x3997130491272Bd1B8a3aa84Fcf5d238F2126eb4" + }, + { + "id": "escalationGameClaimDelegate", + "label": "Escalation Claim Checkpoint Delegate", + "address": "0x26CA07838f50Fd03d9b586f8780F42De9877286f" + }, + { + "id": "escalationGameFactory", + "label": "Escalation Game Factory", + "address": "0xEc0d2a449F6Ec70998043b18F680bC42Cf2f3249" + }, + { + "id": "securityPoolFactory", + "label": "Security Pool Factory", + "address": "0x49De932c947343ADb87508280f99251A3b326283" + }, { "id": "escalationGameProofVerifier", "label": "Escalation Game Proof Verifier", diff --git a/knip.json b/knip.json index eb4a33eab..449e3d76d 100644 --- a/knip.json +++ b/knip.json @@ -1,36 +1,53 @@ { - "entry": [ - "docs/charts/chartRuntime.ts", - "solidity/ts/compile.ts", - "solidity/ts/gas-costs.ts", - "solidity/ts/tests/**/*.ts", - "solidity/ts/fuzz/**/*.ts", - "solidity/ts/types/bun-test.d.ts", - "solidity/ts/types/index.d.ts", - "ui/ts/tests/**/*.ts", - "ui/ts/tests/**/*.tsx", - "ui/ts/index.ts", - "ui/build/vendor.mts", - "ui/build/tests.mts" - ], - "project": [ - "docs/charts/**/*.ts", - "scripts/uniswap-deployment.mts", - "solidity/ts/**/*.ts", - "ui/ts/**/*.ts", - "ui/ts/**/*.tsx", - "ui/build/**/*.mts" - ], - "ignoreDependencies": [ - "better-typescript-lib" - ], - "ignoreIssues": { - "solidity/ts/**/*.ts": ["unlisted"] + "workspaces": { + ".": { + "entry": ["docs/charts/chartRuntime.ts", "solidity/ts/compile.ts", "solidity/ts/gas-costs.ts", "solidity/ts/tests/**/*.ts", "solidity/ts/fuzz/**/*.ts", "solidity/ts/types/bun-test.d.ts", "solidity/ts/types/index.d.ts"], + "project": ["docs/charts/**/*.ts", "scripts/uniswap-deployment.mts", "solidity/ts/**/*.ts"], + "ignoreIssues": { + "solidity/ts/**/*.ts": ["unlisted"] + }, + "ignore": ["docs/charts/virtualModules.d.ts"] + }, + "ui/coreShared": { + "entry": ["ts/tests/**/*.ts", "ts/tests/**/*.tsx", "build/browserSmoke.mts", "build/vendor.mts", "build/tests.mts"], + "project": ["ts/**/*.ts", "ts/**/*.tsx", "build/**/*.mts"], + "paths": { + "@zoltar/ui-core-shared/*": ["./ts/*"] + }, + "ignore": ["build/production.mts", "build/workers.mts", "ts/simulation/tevmWorker.ts", "ts/simulation/tevmEngine.ts"] + }, + "ui/zoltar": { + "entry": ["ts/tests/**/*.ts", "ts/tests/**/*.tsx", "ts/index.ts"], + "project": ["ts/**/*.ts", "ts/**/*.tsx"], + "paths": { + "@zoltar/ui-core-shared/*": ["../coreShared/ts/*"], + "@zoltar/ui-zoltar/*": ["./ts/*"] + }, + "ignore": ["ts/index.dev.ts", "ts/liveReload.ts", "ts/simulation/tevmWorker.ts"] + }, + "ui/statoblast": { + "entry": ["ts/tests/**/*.ts", "ts/tests/**/*.tsx", "ts/index.ts"], + "project": ["ts/**/*.ts", "ts/**/*.tsx"], + "paths": { + "@zoltar/ui-core-shared/*": ["../coreShared/ts/*"], + "@zoltar/ui-zoltar/*": ["../zoltar/ts/*"], + "@zoltar/ui-statoblast/*": ["./ts/*"] + }, + "ignore": ["ts/index.dev.ts", "ts/liveReload.ts", "ts/simulation/tevmWorker.ts"] + }, + "ui/trading": { + "entry": ["ts/tests/**/*.ts", "ts/tests/**/*.tsx", "ts/index.ts", "ts/simulation/tevmWorker.ts"], + "project": ["ts/**/*.ts", "ts/**/*.tsx"], + "paths": { + "@zoltar/ui-core-shared/*": ["../coreShared/ts/*"], + "@zoltar/ui-zoltar/*": ["../zoltar/ts/*"], + "@zoltar/ui-statoblast/*": ["../statoblast/ts/*"], + "@zoltar/ui-trading/*": ["./ts/*"] + }, + "ignore": ["ts/index.dev.ts", "ts/liveReload.ts"], + "ignoreDependencies": ["bun-types"] + } }, - "ignore": [ - "ui/build/production.mts", - "ui/build/workers.mts", - "ui/ts/index.dev.ts", - "ui/ts/liveReload.ts" - ] + "ignoreDependencies": ["better-typescript-lib", "@preact/signals", "tevm", "@noble/curves", "@noble/hashes", "buffer", "micro-eth-signer", "micro-packed", "source-map", "funtypes", "solc-0-8-28"], + "ignoreUnresolved": ["./bun-test-setup-ui.ts", "../../scripts/sharedBrowserArtifacts.ts"] } diff --git a/package.json b/package.json index 39816187c..a416d6e83 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,9 @@ "tevm": "1.0.0-rc.151" }, "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", "brace-expansion": "5.0.9", "fast-uri": "3.1.5", "nanoid": "3.3.18", @@ -37,7 +40,7 @@ }, "scripts": { "preinstall": "bun ./scripts/link-shared-node-modules.mts", - "refresh:shared-dependencies": "bun ./scripts/ensure-shared-package-fresh.mts --refresh && cd solidity && bun ../scripts/ensure-shared-package-fresh.mts --refresh && cd ../ui && bun ../scripts/ensure-shared-package-fresh.mts --refresh", + "refresh:shared-dependencies": "bun ./scripts/ensure-shared-package-fresh.mts --refresh && cd solidity && bun ../scripts/ensure-shared-package-fresh.mts --refresh && cd ../ui/coreShared && bun ../../scripts/ensure-shared-package-fresh.mts --refresh && cd ../zoltar && bun ../../scripts/ensure-shared-package-fresh.mts --refresh && cd ../statoblast && bun ../../scripts/ensure-shared-package-fresh.mts --refresh && cd ../trading && bun ../../scripts/ensure-shared-package-fresh.mts --refresh && cd ../.. && bun ./scripts/ensure-ui-preact-singleton.mts", "check:shared-dependencies": "bun run refresh:shared-dependencies", "check:unit-terminology": "bun ./scripts/check-unit-terminology.mts", "check:uniswap-deployment-artifact": "bun ./scripts/check-uniswap-deployment-artifact.mts", @@ -47,17 +50,17 @@ "ensure-contract-artifacts": "bun ./scripts/ensure-contract-artifacts.mts", "shared:build": "bun x tsc --project shared/tsconfig.json && bun ./scripts/ensure-contract-artifacts.mts --sync-shared-freshness", "generate:shared-js": "bun run shared:build", - "generate:ui-contract-artifact": "bun ./ui/build/projectArtifacts.mts", + "generate:ui-contract-artifact": "bun ./ui/coreShared/build/projectArtifacts.mts", "generate:ui-vendor": "bun run ui:vendor", "generate:contracts": "bun run compile-contracts", "generate:ui": "bun run generate:shared-js && bun run generate:ui-vendor", "check:generated-artifacts": "bun run generate && bun ./scripts/check-generated-artifacts.mts", "check:generated-clean": "bun run check:generated-artifacts", - "setup": "bun ./scripts/install-frozen.mts && bun ./scripts/install-frozen.mts ui && bun run setup-contracts && bun run ui:vendor && cd ui && bun run refresh:shared-dependency && tsc && bun run build:workers && bun ./build/tests.mts", + "setup": "bun ./scripts/install-frozen.mts && bun ./scripts/install-frozen.mts ui/coreShared && bun ./scripts/install-frozen.mts ui/zoltar && bun ./scripts/install-frozen.mts ui/statoblast && bun ./scripts/install-frozen.mts ui/trading && bun run setup-contracts && bun run ui:build:apps && bun run ui:build:tests", "setup-contracts": "cd solidity && bun run setup", "compile-contracts": "cd solidity && bun run compile-contracts", "generate": "bun run generate:contracts && bun run generate:ui-vendor", - "check:mainnet-deployment": "bun run generate && bun run refresh:shared-dependencies && cd ui && bun x tsc --project tsconfig.json && cd .. && bun ./scripts/check-mainnet-deployment.mts", + "check:mainnet-deployment": "bun run generate && bun run refresh:shared-dependencies && bun run tsc:app && bun ./scripts/check-mainnet-deployment.mts", "check:mainnet-deployment:generated": "bun run generate && bun run refresh:shared-dependencies && bun ./scripts/check-mainnet-deployment.mts", "check:mainnet-deployment:current": "bun ./scripts/check-mainnet-deployment.mts", "tsc:app": "bun ./scripts/run-app-typescript.mts", @@ -68,18 +71,18 @@ "tsc:ci:current": "bun run tsc:docs && bun run tsc:app && bun run tsc:scripts && bun run tsc:solidity:current", "ci:preflight:current": "bun ./scripts/run-ci-preflight.mts", "tsc": "bun run ensure-shared-build && bun run check:shared-dependencies && bun run tsc:docs && bun run tsc:app && bun run tsc:scripts && bun run tsc:solidity", - "ui:setup": "bun ./scripts/install-frozen.mts && bun ./scripts/install-frozen.mts ui && cd solidity && bun run setup && cd .. && bun run ui:vendor && cd ui && bun run refresh:shared-dependency && tsc && bun run build:workers && bun ./build/tests.mts", - "app:build": "bun run generate && cd ui && bun run build && bun run build:tests", + "ui:setup": "bun ./scripts/install-frozen.mts && bun ./scripts/install-frozen.mts ui/coreShared && bun ./scripts/install-frozen.mts ui/zoltar && bun ./scripts/install-frozen.mts ui/statoblast && bun ./scripts/install-frozen.mts ui/trading && bun run setup-contracts && bun run ui:build:apps && bun run ui:build:tests", + "app:build": "bun run generate && bun run ui:build:apps && bun run ui:build:tests", "ui:build": "bun run app:build", - "ui:build:prod": "bun run check:mainnet-deployment && cd ui && bun run build:prod", - "ui:build:prod:current": "bun run check:mainnet-deployment:current && cd ui && bun ./build/production.mts", - "ui:build:prod:prepared": "bun run refresh:shared-dependencies && cd ui && bun x tsc --project tsconfig.json && cd .. && bun run ui:build:prod:current", - "ui:workers": "cd ui && bun ./build/workers.mts", - "app:watch": "bun run app:build && bun ./ui/build/watch.mts", + "ui:build:prod": "bun run generate && bun run ui:build:apps && bun run ui:build:prod:current", + "ui:build:prod:current": "bun run check:mainnet-deployment:current && bun ./ui/coreShared/build/production.mts zoltar && bun ./ui/coreShared/build/production.mts statoblast && bun ./ui/coreShared/build/production.mts trading", + "ui:build:prod:prepared": "bun run refresh:shared-dependencies && bun run tsc:app && bun run ui:build:prod:current", + "ui:workers": "cd ui/coreShared && bun run build:workers", + "app:watch": "bun run app:watch:zoltar", "ui:watch": "bun run app:watch", - "app:serve": "bun run app:build && bun ./ui/dev-server.ts", + "app:serve": "bun run app:serve:zoltar", "ui:serve": "bun run app:serve", - "ui:vendor": "cd ui && bun ./build/vendor.mts", + "ui:vendor": "bun ./ui/coreShared/build/vendor.mts zoltar && bun ./ui/coreShared/build/vendor.mts statoblast && bun ./ui/coreShared/build/vendor.mts trading", "cleanup:foundry-anvil-state": "bun ./scripts/cleanup-foundry-anvil-state.mts", "deploy:testnet": "bun run ensure-contract-artifacts && bun run refresh:shared-dependencies && bun ./scripts/deploy-testnet.mts", "gas-costs": "cd solidity && bun run gas-costs", @@ -88,14 +91,14 @@ "test:run:shard": "bun test --preload ./bun-test-setup-ui.ts --reporter=dots --timeout 300000", "test:run:balanced-shard": "bun ./scripts/run-balanced-test-shard.mts", "test:statoblast": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && bun run tsc:solidity && bun test --preload ./bun-test-setup-solidity.ts --timeout 300000 solidity/ts/tests/statoblast", - "test:security-pool-workflow": "bun run ensure-shared-build && bun run check:shared-dependencies && bun run tsc:app && bun test --preload ./bun-test-setup-ui.ts --timeout 300000 ui/ts/tests/features/security-pools/workflow", - "test:integration:mainnet": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && RUN_MAINNET_INTEGRATION_TESTS=1 bun test --preload ./bun-test-setup-ui.ts --timeout 300000 ui/ts/tests/protocol/uniswapQuoter.integration.test.ts", - "test:integration:mainnet-fork": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && RUN_MAINNET_FORK_INTEGRATION_TESTS=1 bun test --preload ./bun-test-setup-ui.ts --timeout 300000 ./ui/ts/tests/protocol/uniswapQuoter.fork.test.ts", + "test:security-pool-workflow": "bun run ensure-shared-build && bun run check:shared-dependencies && bun run tsc:app && bun test --preload ./bun-test-setup-ui.ts --timeout 300000 ui/statoblast/ts/tests/features/security-pools/workflow", + "test:integration:mainnet": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && RUN_MAINNET_INTEGRATION_TESTS=1 bun test --preload ./bun-test-setup-ui.ts --timeout 300000 ui/zoltar/ts/tests/protocol/uniswapQuoter.integration.test.ts", + "test:integration:mainnet-fork": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && RUN_MAINNET_FORK_INTEGRATION_TESTS=1 bun test --preload ./bun-test-setup-ui.ts --timeout 300000 ./ui/zoltar/ts/tests/protocol/uniswapQuoter.fork.test.ts", "test:auction-fuzz": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && bun run tsc && bun test --timeout 300000 ./solidity/ts/fuzz/auctionTickMath.fuzz.ts", "test:launch-invariants": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && bun run tsc:solidity && bun test --preload ./bun-test-setup-solidity.ts --timeout 300000 solidity/ts/tests/statoblastInvariant.test.ts solidity/ts/tests/statoblast/escalationMigration.test.ts solidity/ts/tests/statoblast/truthAuction.test.ts ./solidity/ts/fuzz/auctionTickMath.fuzz.ts", "test:launch-invariants:run": "bun test --preload ./bun-test-setup-solidity.ts --timeout 300000 solidity/ts/tests/statoblastInvariant.test.ts solidity/ts/tests/statoblast/escalationMigration.test.ts solidity/ts/tests/statoblast/truthAuction.test.ts ./solidity/ts/fuzz/auctionTickMath.fuzz.ts", "update:escalation-game-bytecode-snapshot": "UPDATE_ESCALATION_GAME_BYTECODE_SNAPSHOT=1 bun test solidity/ts/tests/escalationGameInterfaceRegression.test.ts", - "coverage:ui": "bun run ensure-shared-build && bun run check:shared-dependencies && bun test --preload ./bun-test-setup-ui.ts --isolate --coverage --coverage-reporter=lcov --coverage-reporter=text --coverage-dir=coverage/ui ui/ts/tests", + "coverage:ui": "bun run ensure-shared-build && bun run check:shared-dependencies && bun test --preload ./bun-test-setup-ui.ts --isolate --coverage --coverage-reporter=lcov --coverage-reporter=text --coverage-dir=coverage/ui ui/coreShared/ts/tests ui/zoltar/ts/tests ui/statoblast/ts/tests ui/trading/ts/tests", "coverage:contracts:ts": "bun run ensure-contract-artifacts && bun test --timeout 300000 --coverage --coverage-reporter=lcov --coverage-reporter=text --coverage-dir=coverage/contracts-ts solidity/ts/tests", "coverage:contracts:bytecode": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && bun ./scripts/run-solidity-bytecode-coverage.mts", "coverage:contracts:bytecode:shard1": "bun run ensure-contract-artifacts && bun run check:shared-dependencies && bun ./scripts/run-solidity-bytecode-coverage.mts --shard 1", @@ -120,7 +123,7 @@ "docs:generate-contract-reference": "bun ./scripts/generate-contract-interaction-reference.mts", "docs:check": "bun run tsc:docs && bun run docs:check-html && bun run docs:check-charts && bun run docs:check-runtime && bun run docs:check-contract-reference && bun run docs:check-index && bun run docs:check-examples && bun run docs:check-content && bun run docs:check-reference-values && bun run check:mainnet-deployment:generated", "docs:check:current": "bun run tsc:docs && bun run docs:check-html && bun run docs:check-charts && bun run docs:check-runtime && bun run docs:check-contract-reference && bun run docs:check-index && bun run docs:check-examples && bun run docs:check-content && bun run docs:check-reference-values && bun run check:mainnet-deployment:current", - "check:static": "bunx @biomejs/biome check package.json .prettierrc.json shared/package.json ui/package.json solidity/package.json tsconfig.scripts.json bun-test-setup*.ts README.md AGENTS.md .codex/review-contract.md docs scripts shared/ts solidity/ts ui/AGENTS.md ui/ts ui/build ui/dev-server.ts ui/css && bun scripts/lint-no-bare-catch.mts && bun scripts/lint-no-signal-wrapper-comparisons.mts && bun scripts/lint-no-direct-viem-imports.mts && bun scripts/lint-no-nested-solidity-ternaries.mts && bun run lint:ui-layer-boundaries", + "check:static": "bunx @biomejs/biome check package.json .prettierrc.json shared/package.json ui/coreShared/package.json ui/zoltar/package.json ui/statoblast/package.json ui/trading/package.json solidity/package.json tsconfig.scripts.json bun-test-setup*.ts README.md AGENTS.md .codex/review-contract.md docs scripts shared/ts solidity/ts ui/AGENTS.md ui/coreShared/ts ui/coreShared/build ui/coreShared/dev-server.ts ui/coreShared/css ui/zoltar/ts ui/statoblast/ts ui/trading/ts ui/trading/css && bun scripts/lint-no-bare-catch.mts && bun scripts/lint-no-signal-wrapper-comparisons.mts && bun scripts/lint-no-direct-viem-imports.mts && bun scripts/lint-no-nested-solidity-ternaries.mts && bun run lint:ui-layer-boundaries", "check:repository": "bun run check:unit-terminology && bun run check:uniswap-deployment-artifact && bun run lint:ui-strings && bun run check:agents && bun run check:html-structure && bun run check:html-format && bun run check:solidity-format", "check": "bun run check:static && bun run docs:check && bun run check:repository", "check:ci:current": "bun run check:static && bun run docs:check:current && bun run check:repository", @@ -129,39 +132,44 @@ "check:html-structure": "bun scripts/check-html-structure.mts", "check:html-format": "bun scripts/check-html-format.mts", "check:solidity-format": "bun scripts/format-solidity-one-line.mts --check", - "lint": "bun x @biomejs/biome check package.json .prettierrc.json shared/package.json tsconfig.scripts.json bun-test-setup*.ts scripts shared/ts solidity/ts ui/ts ui/build ui/dev-server.ts && bun scripts/lint-no-bare-catch.mts && bun scripts/lint-no-signal-wrapper-comparisons.mts && bun scripts/lint-no-direct-viem-imports.mts && bun scripts/lint-no-nested-solidity-ternaries.mts && bun run lint:solidity-one-line", + "lint": "bun x @biomejs/biome check package.json .prettierrc.json shared/package.json tsconfig.scripts.json bun-test-setup*.ts scripts shared/ts solidity/ts ui/coreShared/ts ui/coreShared/build ui/coreShared/dev-server.ts ui/zoltar/ts ui/statoblast/ts ui/trading/ts ui/trading/css && bun scripts/lint-no-bare-catch.mts && bun scripts/lint-no-signal-wrapper-comparisons.mts && bun scripts/lint-no-direct-viem-imports.mts && bun scripts/lint-no-nested-solidity-ternaries.mts && bun run lint:solidity-one-line", "lint:viem-imports": "bun scripts/lint-no-direct-viem-imports.mts", "lint:ui-layer-boundaries": "bun scripts/lint-ui-layer-boundaries.mts", - "lint:fix": "bun x @biomejs/biome check --write package.json .prettierrc.json shared/package.json tsconfig.scripts.json bun-test-setup*.ts scripts shared/ts solidity/ts ui/ts ui/build ui/dev-server.ts && bun run lint:solidity-one-line:fix", + "lint:fix": "bun x @biomejs/biome check --write package.json .prettierrc.json shared/package.json tsconfig.scripts.json bun-test-setup*.ts scripts shared/ts solidity/ts ui/coreShared/ts ui/coreShared/build ui/coreShared/dev-server.ts ui/zoltar/ts ui/statoblast/ts ui/trading/ts ui/trading/css && bun run lint:solidity-one-line:fix", "lint:bare-catch": "bun scripts/lint-no-bare-catch.mts", "lint:signals": "bun scripts/lint-no-signal-wrapper-comparisons.mts", "lint:solidity-ternaries": "bun scripts/lint-no-nested-solidity-ternaries.mts", "lint:solidity-one-line": "bun scripts/format-solidity-one-line.mts --check", "lint:solidity-one-line:fix": "bun scripts/format-solidity-one-line.mts --write", "lint:ui-strings": "bun scripts/lint-ui-tsx-strings.mts", - "format": "bunx @biomejs/biome format --write package.json .prettierrc.json shared/package.json ui/package.json solidity/package.json tsconfig.scripts.json bun-test-setup*.ts README.md AGENTS.md .codex/review-contract.md docs scripts shared/ts ui/AGENTS.md ui/tsconfig.json ui/build ui/dev-server.ts solidity/ts ui/ts ui/css && bun run format:html && bun run format:solidity", - "format:check": "bunx @biomejs/biome format package.json .prettierrc.json shared/package.json ui/package.json solidity/package.json tsconfig.scripts.json bun-test-setup*.ts README.md AGENTS.md .codex/review-contract.md docs scripts shared/ts ui/AGENTS.md ui/tsconfig.json ui/build ui/dev-server.ts solidity/ts ui/ts ui/css && bun run check:html-format && bun run check:solidity-format", + "format": "bunx @biomejs/biome format --write package.json .prettierrc.json shared/package.json ui/coreShared/package.json ui/zoltar/package.json ui/statoblast/package.json ui/trading/package.json solidity/package.json tsconfig.scripts.json bun-test-setup*.ts README.md AGENTS.md .codex/review-contract.md docs scripts shared/ts ui/AGENTS.md ui/tsconfig.base.json ui/coreShared/build ui/coreShared/dev-server.ts ui/coreShared/css solidity/docs/trading solidity/ts ui/coreShared/ts ui/zoltar/ts ui/statoblast/ts ui/trading/ts ui/trading/css && bun run format:html && bun run format:solidity", + "format:check": "bunx @biomejs/biome format package.json .prettierrc.json shared/package.json ui/coreShared/package.json ui/zoltar/package.json ui/statoblast/package.json ui/trading/package.json solidity/package.json tsconfig.scripts.json bun-test-setup*.ts README.md AGENTS.md .codex/review-contract.md docs scripts shared/ts ui/AGENTS.md ui/tsconfig.base.json ui/coreShared/build ui/coreShared/dev-server.ts ui/coreShared/css solidity/docs/trading solidity/ts ui/coreShared/ts ui/zoltar/ts ui/statoblast/ts ui/trading/ts ui/trading/css && bun run check:html-format && bun run check:solidity-format", "format:solidity": "bun scripts/format-solidity-one-line.mts --write", - "trading:setup": "cd trading && bun run setup", - "trading:compile": "cd trading && bun run compile", - "trading:test": "cd trading && bun run test", - "trading:check": "cd trading && bun run check", - "trading:ui:build": "cd trading && bun run ui:build", - "trading:ui:serve": "cd trading && bun run ui:serve", + "trading:setup": "bun run setup-contracts && bun run trading:ui:build", + "trading:compile": "bun run compile-contracts && bun run shared:build", + "trading:test": "bun run ensure-contract-artifacts && bun test --isolate shared/ts/trading solidity/ts/tests/trading ui/trading/ts/tests", + "trading:check": "bun run trading:test && bun ./scripts/trading/check-no-number-casts.mts", + "trading:deploy:local": "bun run ensure-contract-artifacts && bun ./solidity/ts/trading/deploy/deploy-local.mts", + "trading:coverage:contracts": "bun run ensure-contract-artifacts && bun ./scripts/trading/run-solidity-coverage.mts", + "trading:gas-costs": "bun run ensure-contract-artifacts && TRADING_REPORT_GAS=1 bun test ./solidity/ts/tests/trading/pairRouter.integration.test.ts && bun ./solidity/ts/tests/trading/gas-costs.mts", + "trading:ui:build": "cd ui/trading && bun run build", + "trading:ui:serve": "bun run app:serve:trading", "format:html": "bun scripts/format-html-prose.mts", "knip": "bun x knip", "knip:fix": "bun x knip --fix", "ui:publish:ipfs": "docker build --target publisher -f ui/Dockerfile . -t zoltar-ui-publisher && docker run --rm --add-host=host.docker.internal:host-gateway zoltar-ui-publisher", - "anvil:docker": "docker run --rm --name anvil -p 8545:8545 --entrypoint anvil ghcr.io/foundry-rs/foundry:v1.5.1 --host 0.0.0.0 --port 8545 --block-base-fee-per-gas 0 --gas-price 0 --no-priority-fee --chain-id 1" - }, - "knip": { - "ignoreFiles": [ - "docs/charts/virtualModules.d.ts" - ], - "ignore": [ - "ui/build/workers.mts", - "ui/ts/index.dev.ts", - "ui/ts/liveReload.ts" - ] + "anvil:docker": "docker run --rm --name anvil -p 8545:8545 --entrypoint anvil ghcr.io/foundry-rs/foundry:v1.5.1 --host 0.0.0.0 --port 8545 --block-base-fee-per-gas 0 --gas-price 0 --no-priority-fee --chain-id 1", + "ui:build:apps": "cd ui/coreShared && bun run tsc && cd ../zoltar && bun run build && cd ../statoblast && bun run build && cd ../trading && bun run build", + "ui:build:tests": "cd ui/coreShared && bun run build:tests && cd ../zoltar && bun run build:tests && cd ../statoblast && bun run build:tests && cd ../trading && bun run build:tests", + "app:watch:zoltar": "bun run app:build && bun ./ui/coreShared/build/watch.mts zoltar", + "app:watch:statoblast": "bun run app:build && bun ./ui/coreShared/build/watch.mts statoblast", + "app:watch:trading": "bun run app:build && bun ./ui/coreShared/build/watch.mts trading", + "app:serve:zoltar": "bun run app:build && bun ./ui/coreShared/dev-server.ts zoltar", + "app:serve:statoblast": "bun run app:build && bun ./ui/coreShared/dev-server.ts statoblast", + "app:serve:trading": "bun run app:build && bun ./ui/coreShared/dev-server.ts trading", + "ui:browser-smoke:zoltar": "bun ./ui/coreShared/build/browserSmoke.mts zoltar", + "ui:browser-smoke:statoblast": "bun ./ui/coreShared/build/browserSmoke.mts statoblast", + "ui:browser-smoke:trading": "bun ./ui/coreShared/build/browserSmoke.mts trading", + "ui:browser-smoke": "bun run ui:browser-smoke:zoltar && bun run ui:browser-smoke:statoblast && bun run ui:browser-smoke:trading" } } diff --git a/scripts/changed-files.test.ts b/scripts/changed-files.test.ts index 1e8e961c0..185b5188c 100644 --- a/scripts/changed-files.test.ts +++ b/scripts/changed-files.test.ts @@ -3,14 +3,14 @@ import { getChangedFiles } from './changed-files.mts' test('changed-files combines committed, staged, unstaged, and untracked paths', () => { const changedFiles = getChangedFiles(args => { - if (args.join(' ') === 'diff --name-only --diff-filter=ACMRTUXB origin/main...HEAD') return 'ui/ts/components/Committed.tsx\nshared/ts/Shared.ts\n' - if (args.join(' ') === 'diff --name-only --diff-filter=ACMRTUXB') return 'ui/ts/components/Unstaged.tsx\nshared/ts/Shared.ts\n' - if (args.join(' ') === 'diff --cached --name-only --diff-filter=ACMRTUXB') return 'ui/ts/components/Staged.tsx\n' + if (args.join(' ') === 'diff --name-only --diff-filter=ACMRTUXB origin/main...HEAD') return 'ui/zoltar/ts/components/Committed.tsx\nshared/ts/Shared.ts\n' + if (args.join(' ') === 'diff --name-only --diff-filter=ACMRTUXB') return 'ui/zoltar/ts/components/Unstaged.tsx\nshared/ts/Shared.ts\n' + if (args.join(' ') === 'diff --cached --name-only --diff-filter=ACMRTUXB') return 'ui/zoltar/ts/components/Staged.tsx\n' if (args.join(' ') === 'ls-files --others --exclude-standard') return 'scripts/NewScript.mts\n' return '' }) - expect(changedFiles).toEqual(['scripts/NewScript.mts', 'shared/ts/Shared.ts', 'ui/ts/components/Committed.tsx', 'ui/ts/components/Staged.tsx', 'ui/ts/components/Unstaged.tsx']) + expect(changedFiles).toEqual(['scripts/NewScript.mts', 'shared/ts/Shared.ts', 'ui/zoltar/ts/components/Committed.tsx', 'ui/zoltar/ts/components/Staged.tsx', 'ui/zoltar/ts/components/Unstaged.tsx']) }) test('changed-files surfaces branch diff failures instead of silently skipping them', () => { diff --git a/scripts/check-changed.mts b/scripts/check-changed.mts index 7112066d6..1b227c688 100644 --- a/scripts/check-changed.mts +++ b/scripts/check-changed.mts @@ -6,7 +6,7 @@ function runCommand(command: string, args: string[]) { } const BIOME_COVERED_FILE_PATTERNS = [ - /^(package\.json|\.prettierrc\.json|shared\/package\.json|ui\/package\.json|solidity\/package\.json|tsconfig\.scripts\.json)$/, + /^(package\.json|\.prettierrc\.json|shared\/package\.json|ui\/(?:coreShared|zoltar|statoblast|trading)\/package\.json|solidity\/package\.json|tsconfig\.scripts\.json)$/, /^bun-test-setup[^/]*\.ts$/, /^README\.md$/, /^AGENTS\.md$/, @@ -16,10 +16,14 @@ const BIOME_COVERED_FILE_PATTERNS = [ /^shared\/ts\//, /^solidity\/ts\//, /^ui\/AGENTS\.md$/, - /^ui\/ts\//, - /^ui\/build\//, - /^ui\/dev-server\.ts$/, - /^ui\/css\//, + /^ui\/coreShared\/ts\//, + /^ui\/coreShared\/build\//, + /^ui\/coreShared\/dev-server\.ts$/, + /^ui\/coreShared\/css\//, + /^ui\/zoltar\/ts\//, + /^ui\/statoblast\/ts\//, + /^ui\/trading\/ts\//, + /^ui\/trading\/css\//, ] function isBiomeCoveredChangedFile(filePath: string) { diff --git a/scripts/check-changed.test.ts b/scripts/check-changed.test.ts index 72f8b062f..303c53562 100644 --- a/scripts/check-changed.test.ts +++ b/scripts/check-changed.test.ts @@ -1,8 +1,8 @@ import { expect, test } from 'bun:test' import { getBiomeChangedFiles } from './check-changed.mts' -test('check-changed keeps the review contract and filters unsupported config paths', () => { - const biomeChangedFiles = getBiomeChangedFiles(['.codex/agents/reviewer.toml', '.codex/review-contract.md', 'bunfig.toml', 'solidity/contracts/statoblast/WETH9.sol', 'ui/ts/app/App.tsx', 'scripts/check-changed.mts']) +test('check-changed keeps UI apps and the review contract while filtering unsupported config paths', () => { + const biomeChangedFiles = getBiomeChangedFiles(['.codex/agents/reviewer.toml', '.codex/review-contract.md', 'bunfig.toml', 'solidity/contracts/statoblast/WETH9.sol', 'ui/zoltar/ts/app/App.tsx', 'ui/trading/ts/app/App.tsx', 'ui/trading/css/app.css', 'scripts/check-changed.mts']) - expect(biomeChangedFiles).toEqual(['.codex/review-contract.md', 'ui/ts/app/App.tsx', 'scripts/check-changed.mts']) + expect(biomeChangedFiles).toEqual(['.codex/review-contract.md', 'ui/zoltar/ts/app/App.tsx', 'ui/trading/ts/app/App.tsx', 'ui/trading/css/app.css', 'scripts/check-changed.mts']) }) diff --git a/scripts/check-docs-reference-values.mts b/scripts/check-docs-reference-values.mts index 360ffb740..16d17f953 100644 --- a/scripts/check-docs-reference-values.mts +++ b/scripts/check-docs-reference-values.mts @@ -972,7 +972,7 @@ function assertContractInteractionDistinctions(): void { async function assertProductionSolidityInventory(): Promise { const inventoryDocuments = `${contractInteractionReference}\n${operatorReference}` for (const sourcePath of await listSoliditySources('solidity/contracts')) { - if (sourcePath.startsWith('solidity/contracts/test/')) continue + if (sourcePath.includes('/test/')) continue assert.ok(inventoryDocuments.includes(`../${sourcePath}`), `Contract and operator references must inventory production source ${sourcePath}`) } } diff --git a/scripts/check-generated-artifacts.mts b/scripts/check-generated-artifacts.mts index 1f0ffb234..bc5131776 100644 --- a/scripts/check-generated-artifacts.mts +++ b/scripts/check-generated-artifacts.mts @@ -21,9 +21,40 @@ export type GeneratedArtifactCheckOptions = { runGit?: GitRunner } -const explicitlyRequiredGeneratedOutputs = ['shared/js/.freshness-hash', 'solidity/artifacts/Contracts.json', 'solidity/artifacts/.freshness-hash', 'solidity/.contract-hash.json', 'solidity/ts/types/contractArtifact.ts', 'ui/ts/abis.ts', 'ui/ts/contractArtifact.ts'] - -const generatedReviewPaths = ['shared/js', 'solidity/artifacts', 'solidity/.contract-hash.json', 'solidity/ts/types/contractArtifact.ts', 'ui/js', 'ui/ts/abis.ts', 'ui/ts/contractArtifact.ts', 'ui/ts/deploymentArtifacts.ts', 'ui/ts/deploymentsArtifacts.ts', 'ui/vendor'] +const explicitlyRequiredGeneratedOutputs = [ + 'shared/js/.freshness-hash', + 'solidity/artifacts/Contracts.json', + 'solidity/artifacts/.freshness-hash', + 'solidity/.contract-hash.json', + 'solidity/ts/types/contractArtifact.ts', + 'ui/coreShared/ts/abis.ts', + 'ui/coreShared/ts/contractArtifact.ts', + 'ui/trading/ts/generated/contractArtifact.ts', +] + +const generatedReviewPaths = [ + 'shared/js', + 'solidity/artifacts', + 'solidity/.contract-hash.json', + 'solidity/ts/types/contractArtifact.ts', + 'ui/coreShared/js', + 'ui/zoltar/dist', + 'ui/zoltar/js', + 'ui/statoblast/dist', + 'ui/statoblast/js', + 'ui/trading/dist', + 'ui/trading/js', + ':(glob)**/*.tsbuildinfo', + ':(glob)ui/*/ts/**/*.d.ts', + ':(glob)ui/*/ts/**/*.d.ts.map', + 'ui/coreShared/ts/abis.ts', + 'ui/coreShared/ts/contractArtifact.ts', + 'ui/coreShared/ts/deploymentArtifacts.ts', + 'ui/trading/ts/generated', + 'ui/zoltar/vendor', + 'ui/statoblast/vendor', + 'ui/trading/vendor', +] function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) @@ -82,23 +113,28 @@ export async function getSharedPackageGeneratedOutputs(repositoryRoot: string) { return outputs } -async function getUiImportMapGeneratedOutputs(repositoryRoot: string) { - const indexHtml = await fs.readFile(path.join(repositoryRoot, 'ui/index.html'), 'utf8') +async function getUiImportMapGeneratedOutputs(repositoryRoot: string, appId: string) { + const appRoot = path.join(repositoryRoot, 'ui', appId) + const indexHtml = await fs.readFile(path.join(appRoot, 'index.html'), 'utf8') const importMapMatch = indexHtml.match(/]*\btype\s*=\s*['"]importmap['"][^>]*>([\s\S]*?)<\/script>/i) - if (importMapMatch === null) throw new Error('ui/index.html is missing an import map') + if (importMapMatch === null) throw new Error(`ui/${appId}/index.html is missing an import map`) const importMapText = importMapMatch[1] - if (importMapText === undefined) throw new Error('ui/index.html import map is empty') + if (importMapText === undefined) throw new Error(`ui/${appId}/index.html import map is empty`) const importMap = JSON.parse(importMapText) - if (!isRecord(importMap)) throw new Error('ui/index.html import map must be a JSON object') + if (!isRecord(importMap)) throw new Error(`ui/${appId}/index.html import map must be a JSON object`) const imports = importMap['imports'] - if (!isRecord(imports)) throw new Error('ui/index.html import map imports must be an object') + if (!isRecord(imports)) throw new Error(`ui/${appId}/index.html import map imports must be an object`) const outputs: string[] = [] for (const [specifier, targetPath] of Object.entries(imports)) { - if (typeof targetPath !== 'string') throw new Error(`ui/index.html import map target for ${specifier} must be a string`) + if (typeof targetPath !== 'string') throw new Error(`ui/${appId}/index.html import map target for ${specifier} must be a string`) if (!targetPath.startsWith('./') && !targetPath.startsWith('../')) continue - outputs.push(normalizeRepositoryRelativePath('ui', targetPath)) + if (/^(?:\.\.\/)+shared\//.test(targetPath)) { + outputs.push(targetPath.replace(/^(?:\.\.\/)+/, '')) + continue + } + outputs.push(normalizeRepositoryRelativePath(path.join('ui', appId), targetPath)) } return outputs } @@ -138,7 +174,13 @@ function assertNoTrackedGeneratedPaths(trackedGeneratedPaths: readonly string[]) export async function assertGeneratedArtifactsClean(options: GeneratedArtifactCheckOptions = {}) { const repositoryRoot = options.repositoryRoot ?? defaultRepositoryRoot const runGit = options.runGit ?? createGitRunner(repositoryRoot) - const requiredGeneratedOutputs = new Set([...explicitlyRequiredGeneratedOutputs, ...(await getSharedPackageGeneratedOutputs(repositoryRoot)), ...(await getUiImportMapGeneratedOutputs(repositoryRoot))]) + const requiredGeneratedOutputs = new Set([ + ...explicitlyRequiredGeneratedOutputs, + ...(await getSharedPackageGeneratedOutputs(repositoryRoot)), + ...(await getUiImportMapGeneratedOutputs(repositoryRoot, 'zoltar')), + ...(await getUiImportMapGeneratedOutputs(repositoryRoot, 'statoblast')), + ...(await getUiImportMapGeneratedOutputs(repositoryRoot, 'trading')), + ]) for (const relativePath of requiredGeneratedOutputs) { await assertExists(repositoryRoot, relativePath) diff --git a/scripts/check-generated-artifacts.test.ts b/scripts/check-generated-artifacts.test.ts index 1dc6c963b..71b7408ef 100644 --- a/scripts/check-generated-artifacts.test.ts +++ b/scripts/check-generated-artifacts.test.ts @@ -18,9 +18,15 @@ const generatedFixtureFiles = [ 'solidity/artifacts/.freshness-hash', 'solidity/.contract-hash.json', 'solidity/ts/types/contractArtifact.ts', - 'ui/ts/abis.ts', - 'ui/ts/contractArtifact.ts', - 'ui/vendor/isows/native.js', + 'ui/coreShared/ts/abis.ts', + 'ui/coreShared/ts/contractArtifact.ts', + 'ui/trading/ts/generated/contractArtifact.ts', + 'ui/coreShared/js/index.js', + 'ui/zoltar/dist/index.html', + 'ui/zoltar/tsconfig.tsbuildinfo', + 'ui/zoltar/vendor/isows/native.js', + 'ui/statoblast/vendor/isows/native.js', + 'ui/trading/vendor/isows/native.js', ] async function writeFixtureFile(repositoryRoot: string, relativePath: string, contents: string) { @@ -34,7 +40,31 @@ async function createGeneratedArtifactFixture() { await writeFixtureFile(repositoryRoot, 'shared/package.json', `${JSON.stringify({ exports: { './foo': { default: './js/foo.js' } } }, undefined, '\t')}\n`) await writeFixtureFile( repositoryRoot, - 'ui/index.html', + 'ui/zoltar/index.html', + ``, + ) + await writeFixtureFile( + repositoryRoot, + 'ui/statoblast/index.html', + ``, + ) + await writeFixtureFile( + repositoryRoot, + 'ui/trading/index.html', ` - - diff --git a/trading/ui/ts/app/App.tsx b/trading/ui/ts/app/App.tsx deleted file mode 100644 index 68960da30..000000000 --- a/trading/ui/ts/app/App.tsx +++ /dev/null @@ -1,518 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'preact/hooks' -import type { PublicClient } from '@zoltar/shared/ethereum' -import { demoMarket, demoWalletAccount, demoWalletEthAttoEth, demoWalletRepAttoRep } from '../demo/markets.ts' -import { MarketDetail } from '../features/MarketDetail.tsx' -import { Help, Liquidity, MarketList, Portfolio, SecurityPoolDetails } from '../features/Routes.tsx' -import { LiveTrading, type WalletSummaryState } from '../features/LiveTrading.tsx' -import { TradingDeploymentSetup, type DeploymentWalletState, type TradingDeploymentSetupServices } from '../features/TradingDeploymentSetup.tsx' -import type { DeploymentConfiguration } from '../protocol/config.ts' -import { loadCoreDeployments } from '../protocol/coreDeployments.ts' -import { deploymentConfigurationForPlan, getTradingDeploymentPlan, loadTradingDeploymentStatus, type CoreDeployment } from '../protocol/deployment.ts' -import { createTradingPublicClient, publicErrorMessage, validateRpcChainId } from '../protocol/live.ts' -import { formatUnits, shortAddress } from './format.ts' - -const tradingRoutes = ['markets', 'market', 'liquidity', 'portfolio', 'deploy', 'help'] as const -type TradingRoute = (typeof tradingRoutes)[number] | `security-pool/${string}` | 'not-found' - -export function currentRoute(): TradingRoute { - const route = window.location.hash.replace(/^#\/?/, '') || 'markets' - if (route === 'developer') return 'markets' - return tradingRoutes.find(candidate => candidate === route) ?? (/^security-pool\/0x[0-9a-f]{40}$/i.test(route) ? `security-pool/${route.slice('security-pool/'.length)}` : 'not-found') -} - -export function tradingDocumentTitle(route: TradingRoute) { - let label = `${route.charAt(0).toUpperCase()}${route.slice(1)}` - if (route === 'not-found') label = 'Not found' - if (route === 'market') label = 'Market' - if (route.startsWith('security-pool/')) label = 'Security pool' - return `${label} · Statoblast trading` -} - -function DemoSecurityPoolUnavailable() { - return ( -
-
-
- - ← Markets - -

Security pool

-
-
-
- -
-
- ) -} - -function renderRoute(route: string, scenario: string, market: ReturnType, onWorkflowLockChange: (locked: boolean) => void) { - if (route.toLowerCase() === `security-pool/${market.pool}`.toLowerCase()) return - if (/^security-pool\/0x[0-9a-f]{40}$/i.test(route)) return - if (route === 'market') return - if (route === 'liquidity') return - if (route === 'portfolio') return - if (route === 'help') return - if (route === 'markets') return - return ( -
-
-
-

Page not found

-
-
- - Return to markets - -
- ) -} - -function renderBanner(scenario: string, demo: boolean) { - let runtimeBanner - if (scenario === 'wrong-network') - runtimeBanner = ( - - ) - return ( - <> - {demo ? ( -
- SIMULATED DATA - Demo mode · no live chain claims or transactions -
- ) : null} - {runtimeBanner} - - ) -} - -type LiveDeploymentStatus = 'loading' | 'verified' | 'unavailable' - -export async function resolveCanonicalLiveDeployment(coreDeployments: readonly CoreDeployment[], createPublicClient: (configuration: DeploymentConfiguration) => PublicClient = createTradingPublicClient) { - const core = coreDeployments[0] - if (core === undefined) throw new Error('No canonical network deployment is available') - const plan = getTradingDeploymentPlan(core, 30) - const configuration = deploymentConfigurationForPlan(plan, core.defaultRpcUrl) - const client = createPublicClient(configuration) - validateRpcChainId(await client.getChainId(), core.chainId) - const status = await loadTradingDeploymentStatus(client, plan) - if (!status.factory || !status.router) throw new Error('Trading contracts have not been deployed') - return configuration -} - -async function resolveLiveDeployment() { - return await resolveCanonicalLiveDeployment(await loadCoreDeployments()) -} - -function networkLabel(scenario: string, demo: boolean, liveDeploymentStatus: LiveDeploymentStatus) { - if (scenario === 'wrong-network') return 'Unsupported · requires Anvil 31337' - if (demo) return 'Anvil 31337' - if (liveDeploymentStatus === 'verified') return 'Verified live deployment' - if (liveDeploymentStatus === 'unavailable') return 'Deployment unavailable' - return 'Checking deployment' -} - -function networkToneClass(scenario: string, demo: boolean, liveDeploymentStatus: LiveDeploymentStatus) { - if (scenario === 'wrong-network' || (!demo && liveDeploymentStatus === 'unavailable')) return ' network-pill--warn' - if (!demo && liveDeploymentStatus !== 'verified') return ' network-pill--neutral' - return '' -} - -export type UniverseOption = Readonly<{ id: string; label: string; accessibleLabel?: string }> - -export function UniverseSelector({ options, selectedId, disabled, onChange }: { options: readonly UniverseOption[]; selectedId: string | undefined; disabled: boolean; onChange(selectedId: string): void }) { - const selected = options.find(option => option.id === selectedId) - return ( - - ) -} - -export function WalletSummary({ summary, onRetry }: { summary: WalletSummaryState; onRetry?(): void }) { - if (summary.account === undefined) return null - let ethDisplay = '…' - let repBalance = '…' - if (summary.status === 'error') { - ethDisplay = '—' - repBalance = '—' - } else if (summary.status === 'ready') { - if (summary.ethAttoEth !== undefined) ethDisplay = formatUnits(summary.ethAttoEth, 18, 18) - if (summary.repAttoRep !== undefined) repBalance = formatUnits(summary.repAttoRep, 18, 18) - } - return ( -
- - {summary.account} - {shortAddress(summary.account)} - Loading balances… -
- - ETH - {ethDisplay} - - - REP - {repBalance} - -
-
-
-
- Connected account - {summary.account} -
-
- - ETH - {ethDisplay} - - - REP - {repBalance} - -
- {summary.status === 'error' ? ( - - - {summary.errorLabel ?? 'Balances unavailable'} - - {onRetry === undefined ? null : ( - - )} - - ) : null} -
- {summary.status === 'loading' ? ( - - Loading wallet ETH and current-universe REP balances - - ) : null} -
- ) -} - -function demoUniverseChoices(scenario: string) { - const choices = scenario === 'max-token-ids' ? [demoMarket(scenario), demoMarket('max-token-ids-alt'), demoMarket('baseline'), demoMarket('truth-auction')] : [demoMarket(scenario), demoMarket('baseline'), demoMarket('truth-auction')] - return choices.filter((market, index) => choices.findIndex(candidate => candidate.universeId === market.universeId) === index) -} - -function demoWalletSummary(scenario: string, universeId: bigint, retrySucceeded: boolean): WalletSummaryState { - const selectedUniverseId = universeId.toString() - if (scenario === 'wallet-balance-loading') return { account: demoWalletAccount, ethAttoEth: undefined, repAttoRep: undefined, status: 'loading', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } - if (scenario === 'wallet-balance-error' && !retrySucceeded) return { account: demoWalletAccount, ethAttoEth: undefined, repAttoRep: undefined, status: 'error', error: 'Wallet balance RPC request failed', errorLabel: 'Wallet balance read failed', universeId: selectedUniverseId } - if (scenario === 'wallet-discovery-error' && !retrySucceeded) return { account: demoWalletAccount, ethAttoEth: undefined, repAttoRep: undefined, status: 'error', error: 'No SecurityPool is available in the selected universe', errorLabel: 'No SecurityPool in this universe', universeId: selectedUniverseId } - if (scenario === 'wallet-pool-error' && !retrySucceeded) return { account: demoWalletAccount, ethAttoEth: undefined, repAttoRep: undefined, status: 'error', error: 'The selected SecurityPool could not be read', errorLabel: 'SecurityPool unavailable', universeId: selectedUniverseId } - if (scenario === 'wallet-max-balances') return { account: demoWalletAccount, ethAttoEth: 2n ** 256n - 1n, repAttoRep: 2n ** 256n - 1n, status: 'ready', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } - if (scenario === 'wallet-small-balances') return { account: demoWalletAccount, ethAttoEth: 1n, repAttoRep: 1n, status: 'ready', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } - return { account: demoWalletAccount, ethAttoEth: demoWalletEthAttoEth, repAttoRep: demoWalletRepAttoRep(universeId), status: 'ready', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } -} - -export function walletSummaryForUniverse(summary: WalletSummaryState, selectedUniverseId: string | undefined): WalletSummaryState { - if (summary.universeId === selectedUniverseId) return summary - return { account: summary.account, ethAttoEth: undefined, repAttoRep: undefined, status: summary.account === undefined ? 'disconnected' : 'loading', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } -} - -export function routeOwnsLiveWallet(route: string) { - return route !== 'deploy' && route !== 'help' && route !== 'not-found' -} - -export function walletSummaryAfterRouteChange(summary: WalletSummaryState, previousRoute: string, nextRoute: string, selectedUniverseId: string | undefined): WalletSummaryState { - if (routeOwnsLiveWallet(previousRoute) === routeOwnsLiveWallet(nextRoute)) return summary - return { account: undefined, ethAttoEth: undefined, repAttoRep: undefined, status: 'disconnected', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } -} - -export function compactUniverseId(universeId: string) { - if (universeId.length <= 18) return universeId - return `${universeId.slice(0, 3)}…${universeId.slice(-3)}` -} - -export function compactUniqueUniverseIds(universeIds: readonly string[]) { - if (new Set(universeIds).size !== universeIds.length) throw new Error('Universe IDs must be unique') - let edgeLength = 3 - while (true) { - const labels = universeIds.map(universeId => (universeId.length <= edgeLength * 2 + 1 ? universeId : `${universeId.slice(0, edgeLength)}…${universeId.slice(-edgeLength)}`)) - if (new Set(labels).size === labels.length) return labels - edgeLength++ - } -} - -export function buildLiveUniverseOptions(universeIds: readonly bigint[]): readonly UniverseOption[] { - const ids = universeIds.map(universeId => universeId.toString()) - const compactIds = compactUniqueUniverseIds(ids) - return universeIds.map((universeId, index) => { - const id = ids[index] - const compactId = compactIds[index] - if (id === undefined || compactId === undefined) throw new Error('Universe label generation failed') - return universeId === 0n ? { id, label: 'Genesis universe', accessibleLabel: 'Genesis universe' } : { id, label: `Universe ${compactId}`, accessibleLabel: `Universe ${id}` } - }) -} - -function demoUniverseLabel(market: ReturnType, compactId: string) { - if (market.universeId === 1n && market.universe === 'Genesis universe') return 'Genesis universe' - if (market.universeId === 1n && market.universe === 'Parent universe · forked') return 'Universe 1 · forked' - if (market.universeId === 2n && market.universe === 'Child universe · YES branch') return 'Universe 2 · YES branch' - return `Universe ${compactId}` -} - -function deploymentWalletLabel(state: DeploymentWalletState) { - if (state.connecting) return 'Connecting wallet…' - if (state.account === undefined) return 'Connect wallet' - return shortAddress(state.account) -} - -export function App({ deploymentSetupServices, loadLiveDeployment = resolveLiveDeployment }: { deploymentSetupServices?: TradingDeploymentSetupServices; loadLiveDeployment?: () => Promise } = {}) { - const query = new URLSearchParams(window.location.search) - const demo = query.get('demo') === '1' - const scenario = query.get('scenario') ?? 'baseline' - const initialDemoMarket = demoMarket(scenario) - const [route, setRoute] = useState(currentRoute) - const [liveDeploymentStatus, setLiveDeploymentStatus] = useState('loading') - const [liveConfiguration, setLiveConfiguration] = useState() - const [liveConfigurationError, setLiveConfigurationError] = useState() - const [workflowLocked, setWorkflowLocked] = useState(false) - const [selectedUniverseId, setSelectedUniverseId] = useState(demo ? initialDemoMarket.universeId.toString() : undefined) - const [liveUniverseOptions, setLiveUniverseOptions] = useState([]) - const [liveWalletSummary, setLiveWalletSummary] = useState({ account: undefined, ethAttoEth: undefined, repAttoRep: undefined, status: 'disconnected', error: undefined, errorLabel: undefined, universeId: undefined }) - const [walletSummaryRetryNonce, setWalletSummaryRetryNonce] = useState(0) - const [walletConnectRequestNonce, setWalletConnectRequestNonce] = useState(0) - const [deploymentWalletRequestNonce, setDeploymentWalletRequestNonce] = useState(0) - const [deploymentWalletState, setDeploymentWalletState] = useState({ account: undefined, connecting: false, ready: false }) - const [deploymentSettingsHost, setDeploymentSettingsHost] = useState() - const [demoWalletRetrySucceeded, setDemoWalletRetrySucceeded] = useState(false) - const routeRef = useRef(route) - const workflowLockedRef = useRef(workflowLocked) - routeRef.current = route - workflowLockedRef.current = workflowLocked - const updateWorkflowLock = useCallback((locked: boolean) => { - workflowLockedRef.current = locked - setWorkflowLocked(locked) - }, []) - const updateLiveUniverses = useCallback((universeIds: readonly bigint[], authoritativeSelection: bigint | undefined) => { - const options = buildLiveUniverseOptions(universeIds) - setLiveUniverseOptions(options) - setSelectedUniverseId(current => { - if (current !== undefined && options.some(option => option.id === current)) return current - return authoritativeSelection?.toString() - }) - }, []) - const demoMarkets = demoUniverseChoices(scenario) - const demoCompactUniverseIds = compactUniqueUniverseIds(demoMarkets.map(choice => choice.universeId.toString())) - const demoUniverseOptions = demoMarkets.map((choice, index) => { - const id = choice.universeId.toString() - const compactId = demoCompactUniverseIds[index] - if (compactId === undefined) throw new Error('Demo universe label generation failed') - const label = demoUniverseLabel(choice, compactId) - return { id, label, accessibleLabel: label.includes('…') ? `Universe ${id}` : label } - }) - const market = demoMarkets.find(choice => choice.universeId.toString() === selectedUniverseId) ?? initialDemoMarket - const universeOptions = demo ? demoUniverseOptions : liveUniverseOptions - const showUniverseSelector = route !== 'deploy' && route !== 'help' && (demo || liveDeploymentStatus !== 'unavailable') - const walletSummary = demo ? demoWalletSummary(scenario, market.universeId, demoWalletRetrySucceeded) : walletSummaryForUniverse(liveWalletSummary, selectedUniverseId) - const retryWalletSummary = () => { - if (demo) setDemoWalletRetrySucceeded(true) - else { - setLiveWalletSummary(current => ({ account: current.account, ethAttoEth: undefined, repAttoRep: undefined, status: current.account === undefined ? 'disconnected' : 'loading', error: undefined, errorLabel: undefined, universeId: selectedUniverseId })) - setWalletSummaryRetryNonce(current => current + 1) - } - } - const completeWalletDeployment = useCallback((configuration: DeploymentConfiguration) => { - setLiveConfiguration(configuration) - setLiveConfigurationError(undefined) - setLiveDeploymentStatus('verified') - }, []) - const updateDeploymentWalletState = useCallback((state: DeploymentWalletState) => setDeploymentWalletState(state), []) - const deploymentSetupActive = !demo && route !== 'not-found' && route !== 'help' && (route === 'deploy' || liveDeploymentStatus === 'unavailable') - const displayedRoute = deploymentSetupActive ? 'deploy' : route - useEffect(() => { - const update = () => { - if (workflowLockedRef.current) { - window.history.replaceState(undefined, '', `${window.location.pathname}${window.location.search}#/${routeRef.current}`) - return - } - const nextRoute = currentRoute() - if (!demo) setLiveWalletSummary(current => walletSummaryAfterRouteChange(current, routeRef.current, nextRoute, selectedUniverseId)) - routeRef.current = nextRoute - setRoute(nextRoute) - } - window.addEventListener('hashchange', update) - return () => window.removeEventListener('hashchange', update) - }, [demo, selectedUniverseId]) - useEffect(() => { - window.scrollTo(0, 0) - document.title = tradingDocumentTitle(displayedRoute) - }, [displayedRoute]) - useEffect(() => { - if (demo) return - let active = true - setLiveDeploymentStatus('loading') - void (async () => { - try { - const loaded = await loadLiveDeployment() - if (!active) return - setLiveConfiguration(loaded) - setLiveConfigurationError(undefined) - setLiveDeploymentStatus('verified') - } catch (error) { - if (!active) return - setLiveConfiguration(undefined) - setLiveConfigurationError(publicErrorMessage(error, 'Unable to load the trading deployment')) - setLiveDeploymentStatus('unavailable') - } - })() - return () => { - active = false - } - }, [demo, loadLiveDeployment]) - const resolvedContent = renderRoute(route, scenario, market, updateWorkflowLock) - let content = resolvedContent - if (!demo) { - if (route === 'not-found') content = resolvedContent - else if (route === 'help') content = - else if (route === 'deploy') - content = ( - - ) - else if (liveDeploymentStatus === 'unavailable') - content = ( - - ) - else - content = ( - - ) - } else if (route !== 'not-found' && scenario === 'loading') - content = ( -
-
-
-

Loading markets

-

Reading the current factory index, lifecycle state, and pair reserves…

-
-
-
-
-
-
-
- ) - const banner = renderBanner(scenario, demo) - return ( -
- -
- {banner} -
- event.preventDefault() : undefined}> - S - - Statoblast trading - - - -
- - - {networkLabel(scenario, demo, liveDeploymentStatus)} - - {demo || showUniverseSelector ? : null} - {showUniverseSelector ? : null} - {deploymentSetupActive ? ( - - ) : null} - {deploymentSetupActive ?
setDeploymentSettingsHost(element ?? undefined)} /> : null} - {!demo && liveDeploymentStatus === 'verified' && routeOwnsLiveWallet(route) ? ( - - ) : null} -
-
-
- {content} -
- ) -} diff --git a/trading/ui/ts/components/Status.tsx b/trading/ui/ts/components/Status.tsx deleted file mode 100644 index e7100d2ce..000000000 --- a/trading/ui/ts/components/Status.tsx +++ /dev/null @@ -1,28 +0,0 @@ -export function Status({ tone, children }: { tone: 'good' | 'warn' | 'bad' | 'neutral'; children: preact.ComponentChildren }) { - return ( - - - ) -} - -export function AddressValue({ value }: { value: string }) { - return ( - - {value} - - ) -} - -export function securityPoolHref(value: string) { - return `#/security-pool/${value}` -} - -export function SecurityPoolAddressLink({ value, disabled = false }: { value: string; disabled?: boolean }) { - return ( - event.preventDefault() : undefined}> - - - ) -} diff --git a/tsconfig.json b/tsconfig.json index 65ce3b93a..b2ab7966a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,11 @@ }, "include": [ "shared/ts/**/*.ts", - "ui/ts/**/*.ts", - "ui/ts/**/*.tsx", + "ui/coreShared/ts/**/*.ts", + "ui/coreShared/ts/**/*.tsx", + "ui/zoltar/ts/**/*.ts", + "ui/zoltar/ts/**/*.tsx", + "ui/statoblast/ts/**/*.ts", + "ui/statoblast/ts/**/*.tsx", ], } diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json index 52feff78c..3a4b7c51f 100644 --- a/tsconfig.scripts.json +++ b/tsconfig.scripts.json @@ -18,7 +18,12 @@ "noUnusedLocals": true, "noUnusedParameters": true, "lib": ["ESNext", "DOM"], - "types": ["bun-types", "node"] + "types": ["bun-types", "node"], + "paths": { + "@zoltar/ui-core-shared/*": ["./ui/coreShared/ts/*"], + "@zoltar/ui-zoltar/*": ["./ui/zoltar/ts/*"], + "@zoltar/ui-statoblast/*": ["./ui/statoblast/ts/*"] + } }, - "include": ["bun-test-setup*.ts", "scripts/**/*.mts", "scripts/**/*.ts", "ui/build/**/*.mts", "ui/dev-server.ts"] + "include": ["bun-test-setup*.ts", "scripts/**/*.mts", "scripts/**/*.ts", "ui/coreShared/build/**/*.mts", "ui/coreShared/dev-server.ts"] } diff --git a/ui/AGENTS.md b/ui/AGENTS.md index 2a43e4f45..34f2a6cbb 100644 --- a/ui/AGENTS.md +++ b/ui/AGENTS.md @@ -43,7 +43,7 @@ Card-like surfaces must never be nested inside other card-like surfaces. A scree ## Components and reuse -Inspect shared `ui/ts/components`, the relevant `ui/ts/features/*/components`, and the touched route before introducing a primitive or pattern. +Inspect shared `ui/coreShared/ts/components`, the relevant `ui//ts/features/*/components`, and the touched route before introducing a primitive or pattern. - Reuse a component when its semantic role, behavior, and accessibility match the need. - Adapt composition around the closest shared pattern when that preserves its meaning. @@ -61,7 +61,7 @@ Keep cleanup bounded to the edited component, route, and directly shared primiti - Keep related data near the actions it unlocks and action placement stable across state changes. - Avoid oversized empty panels and cramped control clusters. - Use serif display type only for route or hero headings, mono for labels/addresses/hashes/numbers, and sans-serif for body copy and controls. -- Use semantic variables from `ui/css/tokens.css`; add a semantic token for a genuinely new role instead of a raw one-off color. +- Use semantic variables from `ui/coreShared/css/tokens.css`; add a semantic token for a genuinely new role instead of a raw one-off color. - Reserve stronger accent treatment for active, focused, or meaningful status states. - Keep gradients, glow, shine, and overlays subtle and mostly page-level. @@ -117,6 +117,6 @@ For relevant UI changes, verify: - transaction prerequisite and duplicate-submission guards - desktop and narrow/mobile layouts - long values and dense data -- no edits to generated `ui/js/**` +- no edits to generated `ui/zoltar/js/**`, `ui/statoblast/js/**`, or `ui/trading/js/**` Use the browser-local simulation harness described in root `AGENTS.md`. Capture the current screenshots and record the route, scenario, exact viewport dimensions, states, browser, evidence paths or identifiers, intentional differences, and console or runtime errors for the visual-review handoff. Include trustworthy baseline or before evidence when available. Record the tested coverage in the final response. If browser QA or visual review is skipped, give a concrete reason the changed files cannot affect rendered or interactive behavior. diff --git a/ui/Dockerfile b/ui/Dockerfile index e5dc5bae4..c2a60f9bc 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -2,7 +2,7 @@ # App Setup: Install dependencies and build app # --------------------------------------------- -ARG BUN_VERSION=1.3.13 +ARG BUN_VERSION=1.3.14 FROM oven/bun:${BUN_VERSION}-alpine AS builder @@ -13,7 +13,7 @@ RUN git init # Copy preinstall hook scripts before running bun install COPY ./scripts/ /source/scripts/ -COPY ./ui/scripts/ /source/ui/scripts/ +COPY ./ui/coreShared/scripts/ /source/ui/coreShared/scripts/ # Shared package metadata and source for local package resolution COPY ./shared/package.json /source/shared/package.json @@ -31,47 +31,72 @@ COPY ./solidity/package.json ./solidity/bun.lock /source/solidity/ RUN cd /source/solidity && bun install --frozen-lockfile # Solidity contracts and TypeScript source -COPY ./ui/build/projectArtifacts.mts /source/ui/build/projectArtifacts.mts +COPY ./ui/coreShared/build/appPaths.mts /source/ui/coreShared/build/appPaths.mts +COPY ./ui/coreShared/build/projectArtifacts.mts /source/ui/coreShared/build/projectArtifacts.mts COPY ./solidity/tsconfig.json /source/solidity/tsconfig.json COPY ./solidity/tsconfig-compile.json /source/solidity/tsconfig-compile.json COPY ./solidity/contracts/ /source/solidity/contracts/ COPY ./solidity/ts/ /source/solidity/ts/ -RUN mkdir -p /source/ui/ts && bun run compile-contracts +RUN mkdir -p /source/ui/coreShared/ts /source/ui/zoltar/ts /source/ui/statoblast/ts /source/ui/trading/ts && bun run compile-contracts # Install UI dependencies and vendor browser packages after the cached contract build. -COPY ./ui/package.json ./ui/bun.lock /source/ui/ -RUN cd /source/ui && bun install --frozen-lockfile -COPY ./ui/tsconfig.vendor.json /source/ui/tsconfig.vendor.json -COPY ./ui/build/ /source/ui/build/ -RUN bun run ui:vendor +COPY ./ui/coreShared/package.json /source/ui/coreShared/package.json +COPY ./ui/coreShared/bun.lock /source/ui/coreShared/bun.lock +COPY ./ui/zoltar/package.json /source/ui/zoltar/package.json +COPY ./ui/zoltar/bun.lock /source/ui/zoltar/bun.lock +COPY ./ui/statoblast/package.json /source/ui/statoblast/package.json +COPY ./ui/statoblast/bun.lock /source/ui/statoblast/bun.lock +COPY ./ui/trading/package.json /source/ui/trading/package.json +COPY ./ui/trading/bun.lock /source/ui/trading/bun.lock +RUN bun ./scripts/install-frozen.mts ui/coreShared && bun ./scripts/install-frozen.mts ui/zoltar && bun ./scripts/install-frozen.mts ui/statoblast && bun ./scripts/install-frozen.mts ui/trading +COPY ./ui/coreShared/build/ /source/ui/coreShared/build/ +RUN bun ./ui/coreShared/build/vendor.mts zoltar && bun ./ui/coreShared/build/vendor.mts statoblast && bun ./ui/coreShared/build/vendor.mts trading # UI TypeScript source -COPY ./ui/index.html /source/ui/index.html COPY ./docs/mainnet-deployment-addresses.json /source/docs/mainnet-deployment-addresses.json COPY ./docs/sepolia-deployment-addresses.json /source/docs/sepolia-deployment-addresses.json -COPY ./ui/tsconfig.json /source/ui/tsconfig.json -COPY ./ui/css/ /source/ui/css/ -COPY ./ui/ts/ /source/ui/ts/ -COPY ./ui/favicon.ico /source/ui/favicon.ico -COPY ./ui/favicon.svg /source/ui/favicon.svg - -# Refresh local packages, type-check, validate deployment data, and build the UI. -RUN bun run ui:build:prod:prepared +COPY ./ui/tsconfig.base.json /source/ui/tsconfig.base.json +COPY ./ui/coreShared/tsconfig.json /source/ui/coreShared/tsconfig.json +COPY ./ui/zoltar/tsconfig.json /source/ui/zoltar/tsconfig.json +COPY ./ui/statoblast/tsconfig.json /source/ui/statoblast/tsconfig.json +COPY ./ui/trading/tsconfig.json /source/ui/trading/tsconfig.json +COPY ./ui/zoltar/index.html /source/ui/zoltar/index.html +COPY ./ui/statoblast/index.html /source/ui/statoblast/index.html +COPY ./ui/trading/index.html /source/ui/trading/index.html +COPY ./ui/coreShared/css/ /source/ui/coreShared/css/ +COPY ./ui/coreShared/ts/ /source/ui/coreShared/ts/ +COPY ./ui/zoltar/ts/ /source/ui/zoltar/ts/ +COPY ./ui/statoblast/ts/ /source/ui/statoblast/ts/ +COPY ./ui/trading/ts/ /source/ui/trading/ts/ +COPY ./ui/trading/css/ /source/ui/trading/css/ +COPY ./ui/trading/build/ /source/ui/trading/build/ +COPY ./ui/coreShared/favicon.ico /source/ui/coreShared/favicon.ico +COPY ./ui/coreShared/favicon.svg /source/ui/coreShared/favicon.svg + +# Refresh local packages, type-check, validate deployment data, and production-build both apps. +RUN cd /source/ui/coreShared && bun x tsc --project tsconfig.json +RUN cd /source/ui/zoltar && bun x tsc --project tsconfig.json && bun ../coreShared/build/production.mts zoltar +RUN cd /source/ui/statoblast && bun x tsc --project tsconfig.json && bun ../coreShared/build/production.mts statoblast +RUN cd /source/ui/trading && bun x tsc --project tsconfig.json && bun ../coreShared/build/production.mts trading # ---------------------------------------------------- # Local runtime: Serve the production UI over plain HTTP # ---------------------------------------------------- -# start.bat selects this target explicitly, so no IPFS layers are built or run. +# Local runtime builds select this target explicitly, so no IPFS layers are built or run. FROM oven/bun:${BUN_VERSION}-alpine AS local-runtime WORKDIR /app -COPY --from=builder --chown=bun:bun /source/ui/dist/ /app/ui/ -COPY --chown=bun:bun ./ui/build/dockerServe.mts /app/dockerServe.mts +COPY --from=builder --chown=bun:bun /source/ui/zoltar/dist/ /app/ui/zoltar/ +COPY --from=builder --chown=bun:bun /source/ui/statoblast/dist/ /app/ui/statoblast/ +COPY --from=builder --chown=bun:bun /source/ui/trading/dist/ /app/ui/trading/ +COPY --chown=bun:bun ./ui/coreShared/build/dockerServe.mts /app/dockerServe.mts USER bun EXPOSE 8080 +# Select the application the runtime serves; override with `docker run -e UI_APP=statoblast`. +ENV UI_APP=zoltar HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD [ "bun", "-e", "const response = await fetch('http://127.0.0.1:8080/'); process.exit(response.ok ? 0 : 1)" ] CMD [ "bun", "/app/dockerServe.mts" ] @@ -96,11 +121,22 @@ RUN sed -i 's/URIs/# URIs/g' /etc/apt/sources.list.d/debian.sources && \ COPY --from=ipfs-kubo /usr/local/bin/ipfs /usr/local/bin/ipfs # Copy only the deployable UI artifacts (excluding node_modules, source files, build tools) -COPY --from=builder /source/ui/dist/index.html /export/index.html -COPY --from=builder /source/ui/dist/favicon.ico /export/favicon.ico -COPY --from=builder /source/ui/dist/favicon.svg /export/favicon.svg -COPY --from=builder /source/ui/dist/assets/ /export/assets/ -COPY --from=builder /source/ui/dist/css/ /export/css/ +COPY --from=builder /source/ui/zoltar/dist/index.html /export/zoltar/index.html +COPY --from=builder /source/ui/zoltar/dist/favicon.ico /export/zoltar/favicon.ico +COPY --from=builder /source/ui/zoltar/dist/favicon.svg /export/zoltar/favicon.svg +COPY --from=builder /source/ui/zoltar/dist/assets/ /export/zoltar/assets/ +COPY --from=builder /source/ui/zoltar/dist/css/ /export/zoltar/css/ +COPY --from=builder /source/ui/statoblast/dist/index.html /export/statoblast/index.html +COPY --from=builder /source/ui/statoblast/dist/favicon.ico /export/statoblast/favicon.ico +COPY --from=builder /source/ui/statoblast/dist/favicon.svg /export/statoblast/favicon.svg +COPY --from=builder /source/ui/statoblast/dist/assets/ /export/statoblast/assets/ +COPY --from=builder /source/ui/statoblast/dist/css/ /export/statoblast/css/ +COPY --from=builder /source/ui/trading/dist/index.html /export/trading/index.html +COPY --from=builder /source/ui/trading/dist/favicon.ico /export/trading/favicon.ico +COPY --from=builder /source/ui/trading/dist/favicon.svg /export/trading/favicon.svg +COPY --from=builder /source/ui/trading/dist/core-deployments.json /export/trading/core-deployments.json +COPY --from=builder /source/ui/trading/dist/assets/ /export/trading/assets/ +COPY --from=builder /source/ui/trading/dist/css/ /export/trading/css/ RUN ipfs init @@ -110,7 +146,7 @@ RUN ipfs add --cid-version 1 --quieter --only-hash --recursive /export > ipfs_ha # Print the hash for visibility RUN cat ipfs_hash.txt -COPY --chmod=755 ./ui/scripts/docker-entrypoint.sh /entrypoint.sh +COPY --chmod=755 ./ui/coreShared/scripts/docker-entrypoint.sh /entrypoint.sh WORKDIR /temp diff --git a/ui/build/tests.mts b/ui/build/tests.mts deleted file mode 100644 index fedd4347f..000000000 --- a/ui/build/tests.mts +++ /dev/null @@ -1,62 +0,0 @@ -import * as path from 'path' -import * as url from 'url' -import { promises as fs } from 'fs' -import * as process from 'node:process' - -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const UI_ROOT_PATH = path.join(directoryOfThisFile, '..') -const TEST_SOURCE_ROOT_PATH = path.join(UI_ROOT_PATH, 'ts', 'tests') -const TEST_OUTPUT_ROOT_PATH = path.join(UI_ROOT_PATH, 'js', 'tests') - -async function getAllFiles(dirPath: string, fileList: string[] = []) { - const entries = await fs.readdir(dirPath, { withFileTypes: true }) - for (const entry of entries) { - const entryPath = path.join(dirPath, entry.name) - if (entry.isDirectory()) { - await getAllFiles(entryPath, fileList) - } else { - fileList.push(entryPath) - } - } - return fileList -} - -async function buildTests() { - const testFiles = (await getAllFiles(TEST_SOURCE_ROOT_PATH)).filter(filePath => filePath.endsWith('.ts') || filePath.endsWith('.tsx')) - await fs.rm(TEST_OUTPUT_ROOT_PATH, { recursive: true, force: true }) - await fs.mkdir(TEST_OUTPUT_ROOT_PATH, { recursive: true }) - for (const testFile of testFiles) { - const source = await fs.readFile(testFile, 'utf8') - const loader = path.extname(testFile) === '.tsx' ? 'tsx' : 'ts' - let transformSource = source - if (loader === 'tsx') { - const hasH = /\bimport\s*\{[^}]*\bh\b[^}]*\}\s*from\s*['"]preact['"]/.test(source) - const hasFragment = /\bimport\s*\{[^}]*\bFragment\b[^}]*\}\s*from\s*['"]preact['"]/.test(source) - if (!hasH && !hasFragment) { - transformSource = `import { h, Fragment } from 'preact'\n${source}` - } else if (!hasH) { - transformSource = `import { h } from 'preact'\n${source}` - } else if (!hasFragment) { - transformSource = `import { Fragment } from 'preact'\n${source}` - } - } - const code = await new Bun.Transpiler({ - loader, - tsconfig: { - compilerOptions: { - jsx: 'react', - jsxFactory: 'h', - jsxFragmentFactory: 'Fragment', - }, - }, - }).transform(transformSource) - const outputFile = path.join(TEST_OUTPUT_ROOT_PATH, `${path.relative(TEST_SOURCE_ROOT_PATH, testFile).replace(/\.[^.]+$/, '')}.js`) - await fs.mkdir(path.dirname(outputFile), { recursive: true }) - await fs.writeFile(outputFile, code) - } -} - -buildTests().catch(error => { - console.error(error) - process.exit(1) -}) diff --git a/ui/coreShared/build/appPaths.mts b/ui/coreShared/build/appPaths.mts new file mode 100644 index 000000000..b96f4412c --- /dev/null +++ b/ui/coreShared/build/appPaths.mts @@ -0,0 +1,109 @@ +import * as path from 'node:path' +import * as process from 'node:process' +import * as url from 'node:url' + +export const UI_APP_IDS = ['zoltar', 'statoblast', 'trading'] as const +export type UiAppId = (typeof UI_APP_IDS)[number] +export type UiPackageId = 'coreShared' | UiAppId + +export function getUiAppDependencyOrder(appId: UiAppId): readonly UiPackageId[] { + if (appId === 'trading') return ['coreShared', 'zoltar', 'statoblast', 'trading'] + return appId === 'statoblast' ? ['coreShared', 'zoltar', 'statoblast'] : ['coreShared', 'zoltar'] +} + +export function isUiAppId(candidate: string): candidate is UiAppId { + return (UI_APP_IDS as readonly string[]).includes(candidate) +} + +export function parseUiAppId(candidate: string | undefined, context: string): UiAppId { + if (candidate === undefined || candidate === '') throw new Error(`Missing UI app ID for ${context}; expected one of: ${UI_APP_IDS.join(', ')}`) + if (!isUiAppId(candidate)) throw new Error(`Unknown UI app ID '${candidate}' for ${context}; expected one of: ${UI_APP_IDS.join(', ')}`) + return candidate +} + +export function parseUiAppIdFromProcess(context: string): UiAppId { + return parseUiAppId(process.argv[2] ?? process.env['UI_APP'], context) +} + +export type UiAppPaths = { + readonly appId: UiAppId + readonly repositoryRoot: string + readonly uiRoot: string + readonly coreSharedRoot: string + readonly appRoot: string + readonly appSourceRoot: string + readonly appGeneratedJsRoot: string + readonly appDistRoot: string + readonly appDistAssetsRoot: string + readonly appIndexHtml: string + readonly appEntrypoint: string + readonly workerEntrypoint: string + readonly coreSharedCssRoot: string + readonly faviconIco: string + readonly faviconSvg: string + readonly vendorBuildScript: string + readonly workersBuildScript: string + readonly testsBuildScript: string + readonly productionBuildScript: string + readonly projectArtifactsScript: string + readonly bundlerPathsScript: string + readonly devServerScript: string + readonly sharedSourceRoot: string + readonly sharedGeneratedJsRoot: string +} + +const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) + +export function getUiAppPaths(appId: UiAppId): UiAppPaths { + const coreSharedRoot = path.resolve(directoryOfThisFile, '..') + const uiRoot = path.resolve(coreSharedRoot, '..') + const repositoryRoot = path.resolve(uiRoot, '..') + const appRoot = path.join(uiRoot, appId) + const appSourceRoot = path.join(appRoot, 'ts') + const appGeneratedJsRoot = path.join(appRoot, 'js') + const appDistRoot = path.join(appRoot, 'dist') + const buildRoot = path.join(coreSharedRoot, 'build') + return { + appId, + repositoryRoot, + uiRoot, + coreSharedRoot, + appRoot, + appSourceRoot, + appGeneratedJsRoot, + appDistRoot, + appDistAssetsRoot: path.join(appDistRoot, 'assets'), + appIndexHtml: path.join(appRoot, 'index.html'), + appEntrypoint: path.join(appSourceRoot, 'index.ts'), + workerEntrypoint: path.join(appSourceRoot, 'simulation', 'tevmWorker.ts'), + coreSharedCssRoot: path.join(coreSharedRoot, 'css'), + faviconIco: path.join(coreSharedRoot, 'favicon.ico'), + faviconSvg: path.join(coreSharedRoot, 'favicon.svg'), + vendorBuildScript: path.join(buildRoot, 'vendor.mts'), + workersBuildScript: path.join(buildRoot, 'workers.mts'), + testsBuildScript: path.join(buildRoot, 'tests.mts'), + productionBuildScript: path.join(buildRoot, 'production.mts'), + projectArtifactsScript: path.join(buildRoot, 'projectArtifacts.mts'), + bundlerPathsScript: path.join(buildRoot, 'bundlerPaths.mts'), + devServerScript: path.join(coreSharedRoot, 'dev-server.ts'), + sharedSourceRoot: path.join(repositoryRoot, 'shared', 'ts'), + sharedGeneratedJsRoot: path.join(repositoryRoot, 'shared', 'js'), + } +} + +export function getUiCoreSharedPaths() { + const coreSharedRoot = path.resolve(directoryOfThisFile, '..') + const uiRoot = path.resolve(coreSharedRoot, '..') + const repositoryRoot = path.resolve(uiRoot, '..') + return { + repositoryRoot, + uiRoot, + coreSharedRoot, + coreSharedSourceRoot: path.join(coreSharedRoot, 'ts'), + coreSharedGeneratedJsRoot: path.join(coreSharedRoot, 'js'), + coreSharedTestSourceRoot: path.join(coreSharedRoot, 'ts', 'tests'), + coreSharedTestOutputRoot: path.join(coreSharedRoot, 'js', 'tests'), + sharedSourceRoot: path.join(repositoryRoot, 'shared', 'ts'), + sharedGeneratedJsRoot: path.join(repositoryRoot, 'shared', 'js'), + } +} diff --git a/ui/coreShared/build/appPaths.test.ts b/ui/coreShared/build/appPaths.test.ts new file mode 100644 index 000000000..85023a272 --- /dev/null +++ b/ui/coreShared/build/appPaths.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, test } from 'bun:test' +import * as fs from 'node:fs' +import * as path from 'node:path' +import * as url from 'node:url' +import { UI_APP_IDS, getUiAppDependencyOrder, getUiAppPaths, getUiCoreSharedPaths, isUiAppId, parseUiAppId, type UiAppId } from './appPaths.mts' + +const repositoryRoot = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..', '..', '..') + +test('UI_APP_IDS lists exactly the supported applications', () => { + expect(UI_APP_IDS).toEqual(['zoltar', 'statoblast', 'trading']) +}) + +test('getUiAppDependencyOrder preserves the UI package DAG', () => { + expect(getUiAppDependencyOrder('zoltar')).toEqual(['coreShared', 'zoltar']) + expect(getUiAppDependencyOrder('statoblast')).toEqual(['coreShared', 'zoltar', 'statoblast']) + expect(getUiAppDependencyOrder('trading')).toEqual(['coreShared', 'zoltar', 'statoblast', 'trading']) +}) + +test('isUiAppId accepts supported applications and rejects unknown values', () => { + expect(isUiAppId('zoltar')).toBe(true) + expect(isUiAppId('statoblast')).toBe(true) + expect(isUiAppId('trading')).toBe(true) + expect(isUiAppId('')).toBe(false) + expect(isUiAppId('coreShared')).toBe(false) + expect(isUiAppId('Zoltar')).toBe(false) +}) + +test('parseUiAppId rejects missing and unknown IDs with the supported list', () => { + expect(() => parseUiAppId(undefined, 'the test')).toThrow(/Missing UI app ID for the test.*zoltar, statoblast, trading/) + expect(() => parseUiAppId('coreShared', 'the test')).toThrow(/Unknown UI app ID 'coreShared'.*zoltar, statoblast, trading/) + expect(parseUiAppId('zoltar', 'the test')).toBe('zoltar') + expect(parseUiAppId('statoblast', 'the test')).toBe('statoblast') + expect(parseUiAppId('trading', 'the test')).toBe('trading') +}) + +for (const appId of UI_APP_IDS) { + test(`getUiAppPaths resolves existing locations for ${appId}`, () => { + const paths = getUiAppPaths(appId) + expect(paths.repositoryRoot).toBe(repositoryRoot) + expect(paths.uiRoot).toBe(path.join(repositoryRoot, 'ui')) + expect(paths.coreSharedRoot).toBe(path.join(repositoryRoot, 'ui', 'coreShared')) + expect(paths.appRoot).toBe(path.join(repositoryRoot, 'ui', appId)) + expect(paths.appDistRoot).toBe(path.join(paths.appRoot, 'dist')) + expect(paths.appDistAssetsRoot).toBe(path.join(paths.appRoot, 'dist', 'assets')) + expect(paths.appIndexHtml).toBe(path.join(paths.appRoot, 'index.html')) + expect(paths.appEntrypoint).toBe(path.join(paths.appSourceRoot, 'index.ts')) + expect(paths.workerEntrypoint).toBe(path.join(paths.appSourceRoot, 'simulation', 'tevmWorker.ts')) + + for (const existingPath of [paths.appIndexHtml, paths.appEntrypoint, paths.workerEntrypoint, paths.faviconIco, paths.faviconSvg, paths.coreSharedCssRoot, paths.sharedSourceRoot]) { + expect(fs.existsSync(existingPath), `expected ${existingPath} to exist`).toBe(true) + } + }) +} + +test('getUiCoreSharedPaths resolves the repository root from ui/coreShared/build', () => { + const paths = getUiCoreSharedPaths() + expect(paths.repositoryRoot).toBe(repositoryRoot) + expect(paths.coreSharedTestSourceRoot).toBe(path.join(repositoryRoot, 'ui', 'coreShared', 'ts', 'tests')) + expect(fs.existsSync(path.join(paths.repositoryRoot, 'package.json'))).toBe(true) +}) + +test('vendor and worker builders consume the centralized app path helper', () => { + for (const buildScript of ['vendor.mts', 'workers.mts']) { + const source = fs.readFileSync(path.join(import.meta.dir, buildScript), 'utf8') + expect(source).toContain("from './appPaths.mts'") + expect(source).not.toMatch(/const APP_IDS\s*=/) + } +}) + +async function waitForDevelopmentServer(serverUrl: string) { + for (let attempt = 0; attempt < 100; attempt++) { + try { + const response = await fetch(serverUrl) + if (response.ok) return await response.text() + } catch (error) { + if (!(error instanceof Error) || !error.message.includes('Unable to connect')) throw error + } + await Bun.sleep(20) + } + throw new Error(`Development server did not start at ${serverUrl}`) +} + +describe('split development server paths', () => { + for (const [appId, port] of [ + ['zoltar', 12346], + ['statoblast', 12347], + ['trading', 4163], + ] as const) { + test(`serves the ${appId} application root`, async () => { + const titles: Record = { statoblast: 'Augur Statoblast', trading: 'Statoblast trading', zoltar: 'Zoltar' } + const paths = getUiAppPaths(appId) + const processHandle = Bun.spawn([process.execPath, paths.devServerScript, appId], { stderr: 'pipe', stdout: 'pipe' }) + try { + const html = await waitForDevelopmentServer(`http://127.0.0.1:${port.toString()}/`) + expect(html).toContain(titles[appId]) + } finally { + processHandle.kill() + await processHandle.exited + } + }) + } + + for (const [label, args, expected] of [ + ['missing', [], 'Missing UI app ID'], + ['unknown', ['unknown'], "Unknown UI app ID 'unknown'"], + ] as const) { + test(`rejects a ${label} application ID`, async () => { + const processHandle = Bun.spawn([process.execPath, getUiAppPaths('zoltar').devServerScript, ...args], { stderr: 'pipe', stdout: 'pipe' }) + const stderr = await new Response(processHandle.stderr).text() + expect(await processHandle.exited).not.toBe(0) + expect(stderr).toContain(expected) + }) + } +}) diff --git a/ui/coreShared/build/browserSmoke.mts b/ui/coreShared/build/browserSmoke.mts new file mode 100644 index 000000000..2314bc581 --- /dev/null +++ b/ui/coreShared/build/browserSmoke.mts @@ -0,0 +1,494 @@ +import { type ChildProcess, spawn } from 'node:child_process' +import * as fs from 'node:fs/promises' +import * as os from 'node:os' +import * as path from 'node:path' +import * as process from 'node:process' +import { getChromiumPath, withChromiumTestLock } from './chromiumPath.js' +import { getUiAppPaths, parseUiAppIdFromProcess, type UiAppId } from './appPaths.mts' + +const MOUNT_TIMEOUT_MILLISECONDS = 120_000 +const DEVTOOLS_COMMAND_TIMEOUT_MILLISECONDS = 15_000 +const DEVTOOLS_INITIALIZATION_TIMEOUT_MILLISECONDS = 60_000 +const BROWSER_TERMINATION_TIMEOUT_MILLISECONDS = 2_000 + +type PageIssue = { + readonly kind: 'pageerror' | 'console-error' | 'import-map' | 'request-failed' | 'worker' | 'mount' + readonly detail: string +} + +type NetworkRequest = { readonly resourceType: string; readonly url: string } + +type ChromiumCommand = (method: string, params?: Record, sessionId?: string) => Promise +type ChromiumCommandSocket = EventTarget & { send(data: string): void } + +type BrowserSmokeState = { + body: string + height: number + hasMain: boolean + title: string + width: number +} + +export async function waitForBrowserExit(browser: ChildProcess): Promise { + if (browser.exitCode !== null || browser.signalCode !== null) return + await new Promise((resolve, reject) => { + browser.once('error', reject) + browser.once('exit', () => resolve()) + }) +} + +type BrowserExitOutcome = { readonly exited: boolean; readonly processError?: Error } + +const waitForBrowserExitWithin = async (browser: ChildProcess, timeoutMilliseconds: number): Promise => { + if (browser.exitCode !== null || browser.signalCode !== null || browser.pid === undefined) return { exited: true } + return await new Promise(resolve => { + let processError: Error | undefined + const finish = (exited: boolean) => { + clearTimeout(timeoutId) + browser.off('error', handleProcessError) + browser.off('exit', handleTermination) + resolve({ exited, ...(processError === undefined ? {} : { processError }) }) + } + const handleTermination = () => finish(true) + const handleProcessError = (error: Error) => { + processError = error + } + const timeoutId = setTimeout(() => finish(false), timeoutMilliseconds) + browser.on('error', handleProcessError) + browser.once('exit', handleTermination) + }) +} + +const signalBrowserAndWait = async (browser: ChildProcess, signal: NodeJS.Signals, timeoutMilliseconds: number): Promise => { + const exitOutcome = waitForBrowserExitWithin(browser, timeoutMilliseconds) + browser.kill(signal) + return await exitOutcome +} + +export async function terminateBrowserProcess( + browser: ChildProcess, + profilePath: string, + { forceTimeoutMilliseconds = BROWSER_TERMINATION_TIMEOUT_MILLISECONDS, gracefulTimeoutMilliseconds = BROWSER_TERMINATION_TIMEOUT_MILLISECONDS }: { readonly forceTimeoutMilliseconds?: number; readonly gracefulTimeoutMilliseconds?: number } = {}, +): Promise { + try { + if (browser.exitCode !== null || browser.signalCode !== null || browser.pid === undefined) return + const gracefulOutcome = await signalBrowserAndWait(browser, 'SIGTERM', gracefulTimeoutMilliseconds) + if (gracefulOutcome.exited) return + const forceOutcome = await signalBrowserAndWait(browser, 'SIGKILL', forceTimeoutMilliseconds) + if (!forceOutcome.exited) { + const processError = forceOutcome.processError ?? gracefulOutcome.processError + throw new Error(`Chromium did not exit after SIGKILL within ${forceTimeoutMilliseconds.toString()}ms${processError === undefined ? '' : `: ${processError.message}`}`) + } + } finally { + await fs.rm(profilePath, { force: true, recursive: true }) + } +} + +const awaitInitializationStep = async (browser: ChildProcess, deadline: number, description: string, action: Promise, cancel: () => void): Promise => { + const remainingMilliseconds = deadline - Date.now() + if (remainingMilliseconds <= 0) { + cancel() + throw new Error(`Chromium initialization timed out while ${description}`) + } + let completed = false + let succeeded = false + try { + const value = await new Promise((resolve, reject) => { + const finish = (result: { readonly error: Error } | { readonly value: TValue }) => { + if (completed) return + completed = true + clearTimeout(timeoutId) + browser.off('error', handleError) + browser.off('exit', handleExit) + if ('error' in result) reject(result.error) + else resolve(result.value) + } + const handleError = (error: Error) => finish({ error: new Error(`Chromium failed while ${description}: ${error.message}`) }) + const handleExit = (exitCode: number | null, signalCode: NodeJS.Signals | null) => finish({ error: new Error(`Chromium exited with ${signalCode === null ? `code ${String(exitCode)}` : `signal ${signalCode}`} while ${description}`) }) + const timeoutId = setTimeout(() => finish({ error: new Error(`Chromium initialization timed out while ${description}`) }), remainingMilliseconds) + browser.once('error', handleError) + browser.once('exit', handleExit) + action.then( + value => finish({ value }), + error => finish({ error: error instanceof Error ? error : new Error(String(error)) }), + ) + }) + succeeded = true + return value + } finally { + if (!succeeded) cancel() + } +} + +export async function waitForDevToolsPort({ + assertBrowserAvailable, + maxAttempts, + pollMilliseconds, + readPort, + wait = Bun.sleep, +}: { + readonly assertBrowserAvailable: () => void + readonly maxAttempts?: number + readonly pollMilliseconds: number + readonly readPort: () => Promise + readonly wait?: (milliseconds: number) => Promise +}): Promise { + for (let attempt = 0; maxAttempts === undefined || attempt < maxAttempts; attempt++) { + assertBrowserAvailable() + const port = await readPort() + if (port !== undefined) return port + await wait(pollMilliseconds) + } + return undefined +} + +export function createBrowserSmokeCommandSender(socket: ChromiumCommandSocket, browser: ChildProcess, timeoutMilliseconds = DEVTOOLS_COMMAND_TIMEOUT_MILLISECONDS): ChromiumCommand { + let requestId = 0 + const pending = new Map void; resolve: (value: unknown) => void }>() + const rejectPending = (message: string) => { + for (const request of pending.values()) request.reject(new Error(message)) + pending.clear() + } + + socket.addEventListener('message', event => { + if (!(event instanceof MessageEvent) || typeof event.data !== 'string') return + const message: unknown = JSON.parse(event.data) + if (typeof message !== 'object' || message === null || !('id' in message) || typeof message.id !== 'number') return + const request = pending.get(message.id) + if (request === undefined) return + pending.delete(message.id) + if ('error' in message) request.reject(new Error(`Chromium DevTools command failed: ${JSON.stringify(message.error)}`)) + else request.resolve('result' in message ? message.result : undefined) + }) + socket.addEventListener('close', () => rejectPending('Chromium DevTools connection closed while commands were pending')) + socket.addEventListener('error', () => rejectPending('Chromium DevTools connection failed while commands were pending')) + browser.once('exit', (exitCode, signalCode) => rejectPending(`Chromium exited with ${signalCode === null ? `code ${String(exitCode)}` : `signal ${signalCode}`} while commands were pending`)) + + return async (method, params = {}, sessionId) => { + if (browser.exitCode !== null || browser.signalCode !== null) throw new Error(`Chromium already exited before DevTools command ${method}`) + requestId += 1 + const id = requestId + return await new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + pending.delete(id) + reject(new Error(`Chromium DevTools command ${method} did not complete within ${timeoutMilliseconds.toString()}ms`)) + }, timeoutMilliseconds) + pending.set(id, { + reject: error => { + clearTimeout(timeoutId) + reject(error) + }, + resolve: value => { + clearTimeout(timeoutId) + resolve(value) + }, + }) + try { + socket.send(JSON.stringify({ id, method, params, ...(sessionId === undefined ? {} : { sessionId }) })) + } catch (error) { + pending.get(id)?.reject(error instanceof Error ? error : new Error(String(error))) + pending.delete(id) + } + }) + } +} + +export function isBrowserSmokeReady(state: BrowserSmokeState, applicationTitle: string, readyText: string | undefined, viewport: { readonly height: number; readonly width: number }) { + const normalizedBody = state.body.toLocaleLowerCase() + const includesText = (text: string) => normalizedBody.includes(text.toLocaleLowerCase()) + const explicitReadyStateReached = readyText !== undefined && includesText(readyText) + return ( + state.hasMain && + state.width === viewport.width && + state.height === viewport.height && + state.body !== '' && + state.body !== 'Loading...' && + includesText(applicationTitle) && + (explicitReadyStateReached || (!state.body.includes('BOOTSTRAPPING') && !state.body.includes('Starting simulation bootstrap'))) && + (readyText === undefined || includesText(readyText)) + ) +} + +function parseViewport(candidate: string | undefined) { + const match = /^(\d+)x(\d+)$/.exec(candidate ?? '1440x900') + if (match === null) throw new Error(`Invalid UI_VIEWPORT '${candidate ?? ''}'; expected WIDTHxHEIGHT.`) + const width = Number(match[1]) + const height = Number(match[2]) + if (width < 1 || height < 1) throw new Error(`Invalid UI_VIEWPORT '${candidate ?? ''}'; dimensions must be positive.`) + return { height, width } +} + +export async function createDevToolsSession( + chromiumPath: string, + pageUrl: string, + viewport: { readonly height: number; readonly width: number }, + { devToolsPortAttempts, initializationTimeoutMilliseconds = DEVTOOLS_INITIALIZATION_TIMEOUT_MILLISECONDS, pollMilliseconds = 50, targetAttempts }: { readonly devToolsPortAttempts?: number; readonly initializationTimeoutMilliseconds?: number; readonly pollMilliseconds?: number; readonly targetAttempts?: number } = {}, +) { + const profilePath = await fs.mkdtemp(path.join(os.tmpdir(), 'zoltar-browser-smoke-')) + let browser: ChildProcess | undefined + let socket: WebSocket | undefined + let cleanedUp = false + const cleanup = async () => { + if (cleanedUp) return + cleanedUp = true + socket?.close() + if (browser === undefined) await fs.rm(profilePath, { force: true, recursive: true }) + else await terminateBrowserProcess(browser, profilePath) + } + let stderrData = '' + try { + browser = spawn(chromiumPath, ['--headless', '--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage', '--remote-debugging-port=0', `--user-data-dir=${profilePath}`, `--window-size=${viewport.width.toString()},${viewport.height.toString()}`, 'about:blank'], { stdio: ['ignore', 'ignore', 'pipe'] }) + let launchError: Error | undefined + browser.once('error', error => { + launchError = error + }) + browser.stderr?.on('data', chunk => { + stderrData += String(chunk) + }) + const initializationDeadline = Date.now() + initializationTimeoutMilliseconds + const assertBrowserAvailable = (initializationPhase: string) => { + if (launchError !== undefined) throw new Error(`Could not launch Chromium: ${launchError.message}`) + if (browser?.exitCode !== null || browser.signalCode !== null) throw new Error(`Chromium exited before DevTools initialized. stderr: ${stderrData.trim()}`) + if (Date.now() >= initializationDeadline) throw new Error(`Chromium initialization timed out while ${initializationPhase}`) + } + const devToolsPort = await waitForDevToolsPort({ + assertBrowserAvailable: () => assertBrowserAvailable('waiting for the DevTools port'), + ...(devToolsPortAttempts === undefined ? {} : { maxAttempts: devToolsPortAttempts }), + pollMilliseconds, + readPort: async () => { + try { + return Number((await fs.readFile(path.join(profilePath, 'DevToolsActivePort'), 'utf8')).split('\n')[0]) + } catch (error) { + if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') throw error + return undefined + } + }, + }) + assertBrowserAvailable('waiting for the DevTools port') + if (devToolsPort === undefined) throw new Error(`Chromium DevTools port did not open. stderr: ${stderrData.trim()}`) + + const issues: PageIssue[] = [] + const networkRequests = new Map() + const workerTargets = new Map() + let lastNetworkActivity = Date.now() + let workerStarted = false + socket = await (async () => { + for (let attempt = 0; targetAttempts === undefined || attempt < targetAttempts; attempt++) { + try { + assertBrowserAvailable('waiting for the Chromium page target') + const fetchController = new AbortController() + const targets = (await awaitInitializationStep( + browser, + initializationDeadline, + 'requesting the DevTools target list', + fetch(`http://127.0.0.1:${devToolsPort}/json/list`, { signal: fetchController.signal }).then(async response => (await response.json()) as Array<{ type: string; webSocketDebuggerUrl: string }>), + () => fetchController.abort(), + )) as Array<{ type: string; webSocketDebuggerUrl: string }> + const page = targets.find(target => target.type === 'page') + if (page !== undefined) { + const ws = new WebSocket(page.webSocketDebuggerUrl) + await awaitInitializationStep( + browser, + initializationDeadline, + 'opening the DevTools WebSocket', + new Promise((resolve, reject) => { + ws.addEventListener('open', () => resolve(), { once: true }) + ws.addEventListener('error', () => reject(new Error('Could not open the Chromium DevTools WebSocket')), { once: true }) + }), + () => ws.close(), + ) + return ws + } + } catch (error) { + if (!(error instanceof TypeError)) throw error + // Chromium target list not ready yet. + } + await Bun.sleep(pollMilliseconds) + } + throw new Error('Could not connect to the Chromium page target') + })() + + const send = createBrowserSmokeCommandSender(socket, browser) + socket.addEventListener('message', event => { + if (typeof event.data !== 'string') return + const message: unknown = JSON.parse(event.data) + if (typeof message !== 'object' || message === null) return + if ('method' in message) { + const { method, params, sessionId } = message as { method: string; params?: Record; sessionId?: string } + const networkRequestKey = (requestId: unknown) => `${sessionId ?? 'page'}:${String(requestId)}` + if (method === 'Runtime.exceptionThrown') { + const exceptionDetails = params?.['exceptionDetails'] as { text?: string; exception?: { description?: string } } | undefined + const detail = exceptionDetails?.exception?.description ?? exceptionDetails?.text ?? 'Unknown page error' + issues.push({ kind: /import.?map/i.test(detail) ? 'import-map' : 'pageerror', detail }) + } + if (method === 'Runtime.consoleAPICalled' && params?.['type'] === 'error') { + const args = (params['args'] as Array<{ value?: unknown; description?: string }> | undefined) ?? [] + const detail = args.map(arg => String(arg.value ?? arg.description ?? '')).join(' ') || 'console.error()' + issues.push({ kind: /import.?map/i.test(detail) ? 'import-map' : 'console-error', detail }) + } + if (method === 'Log.entryAdded') { + const entry = params?.['entry'] as { level?: string; text?: string; url?: string } | undefined + if (entry?.level === 'error') { + const detail = `${entry.text ?? 'Browser log error'}${entry.url === undefined ? '' : ` (${entry.url})`}` + issues.push({ kind: /import.?map/i.test(detail) ? 'import-map' : 'console-error', detail }) + } + } + if (method === 'Network.requestWillBeSent') { + const requestId = params?.['requestId'] + const request = params?.['request'] as { url?: string } | undefined + if (typeof requestId === 'string' && typeof request?.url === 'string') networkRequests.set(networkRequestKey(requestId), { resourceType: String(params?.['type'] ?? ''), url: request.url }) + lastNetworkActivity = Date.now() + } + if (method === 'Network.responseReceived') { + const requestId = params?.['requestId'] + const response = params?.['response'] as { status?: number; url?: string } | undefined + const request = typeof requestId === 'string' ? networkRequests.get(networkRequestKey(requestId)) : undefined + const resourceType = String(params?.['type'] ?? request?.resourceType ?? '') + const resourceUrl = response?.url ?? request?.url ?? 'unknown URL' + if (typeof response?.status === 'number' && response.status >= 400 && isRequiredBrowserResource(resourceUrl, resourceType)) issues.push({ kind: 'request-failed', detail: `${response.status.toString()} ${resourceType || 'resource'} ${resourceUrl}` }) + lastNetworkActivity = Date.now() + } + if (method === 'Network.loadingFailed') { + const requestId = params?.['requestId'] + const request = typeof requestId === 'string' ? networkRequests.get(networkRequestKey(requestId)) : undefined + const resourceType = String(params?.['type'] ?? request?.resourceType ?? '') + const detail = `${String(params?.['errorText'] ?? 'request failed')} ${resourceType || 'resource'} ${request?.url ?? 'unknown URL'}` + const isBenignCanceledImage = params?.['canceled'] === true && resourceType === 'Image' + if (!isBenignCanceledImage) issues.push({ kind: 'request-failed', detail }) + lastNetworkActivity = Date.now() + } + if (method === 'Target.targetCreated') { + const targetInfo = params?.['targetInfo'] as { targetId?: string; type?: string; url?: string } | undefined + if (targetInfo?.type === 'worker' || targetInfo?.type === 'service_worker') { + workerStarted = true + if (targetInfo.targetId !== undefined) workerTargets.set(targetInfo.targetId, targetInfo.url ?? 'unknown worker URL') + } + } + if (method === 'Target.attachedToTarget') { + const childSessionId = params?.['sessionId'] + const targetInfo = params?.['targetInfo'] as { targetId?: string; type?: string; url?: string } | undefined + if (typeof childSessionId === 'string' && (targetInfo?.type === 'worker' || targetInfo?.type === 'service_worker')) { + workerStarted = true + if (targetInfo.targetId !== undefined) workerTargets.set(targetInfo.targetId, targetInfo.url ?? 'unknown worker URL') + void Promise.all([send('Network.enable', {}, childSessionId), send('Log.enable', {}, childSessionId), send('Runtime.enable', {}, childSessionId)]).catch(error => { + if (!(error instanceof Error) || !error.message.includes('Session with given id not found')) issues.push({ kind: 'worker', detail: error instanceof Error ? error.message : String(error) }) + }) + } + } + if (method === 'Target.targetDestroyed') { + const targetId = params?.['targetId'] + const workerUrl = typeof targetId === 'string' ? workerTargets.get(targetId) : undefined + if (workerUrl !== undefined) issues.push({ kind: 'worker', detail: `Worker terminated before smoke completion: ${workerUrl}` }) + } + return + } + }) + + return { close: cleanup, getLastNetworkActivity: () => lastNetworkActivity, hasWorkerStarted: () => workerStarted, issues, send, pageUrl } + } catch (error) { + await cleanup() + throw error + } +} + +export function isRequiredBrowserResource(resourceUrl: string, resourceType: string) { + return resourceType === 'Script' || resourceType === 'Stylesheet' || /(?:\.m?js|\.css)(?:[?#]|$)/i.test(resourceUrl) || /worker/i.test(resourceUrl) +} + +async function runBrowserSmokeUnlocked(appId: UiAppId, baseUrl: string, options: { readonly mountTimeoutMilliseconds?: number; readonly requireWorker?: boolean } = {}) { + const chromiumPath = getChromiumPath() + if (chromiumPath === undefined) throw new Error('Chromium is required for the browser smoke check. Set CHROMIUM_PATH or install Chromium.') + const route = process.env['UI_BROWSER_ROUTE'] ?? '' + if (route !== '' && !route.startsWith('#')) throw new Error(`Invalid UI_BROWSER_ROUTE '${route}'; expected an empty value or a hash route.`) + const simulationScenario = process.env['UI_SIMULATION_SCENARIO'] ?? (appId === 'trading' ? 'trading' : 'baseline') + const pageUrl = `${baseUrl.replace(/\/$/, '')}/?simulate=1&simScenario=${encodeURIComponent(simulationScenario)}${route}` + const viewport = parseViewport(process.env['UI_VIEWPORT']) + const session = await createDevToolsSession(chromiumPath, pageUrl, viewport) + try { + const { send, issues } = session + const applicationTitles: Record = { statoblast: 'Augur Statoblast', trading: 'Statoblast trading', zoltar: 'Zoltar' } + const applicationTitle = applicationTitles[appId] + const readyText = process.env['UI_BROWSER_READY_TEXT'] + await send('Runtime.enable') + await send('Page.enable') + await send('Network.enable') + await send('Log.enable') + await send('Target.setDiscoverTargets', { discover: true }) + await send('Target.setAutoAttach', { autoAttach: true, flatten: true, waitForDebuggerOnStart: false }) + await send('Emulation.setDeviceMetricsOverride', { deviceScaleFactor: 1, height: viewport.height, mobile: false, width: viewport.width }) + await send('Page.navigate', { url: pageUrl }) + + const start = Date.now() + let mounted = false + let lastObservedState: BrowserSmokeState | undefined + const mountTimeoutMilliseconds = options.mountTimeoutMilliseconds ?? MOUNT_TIMEOUT_MILLISECONDS + while (Date.now() - start < mountTimeoutMilliseconds) { + const result = (await send('Runtime.evaluate', { + expression: `JSON.stringify({ body: document.body?.innerText ?? '', height: window.innerHeight, hasMain: document.querySelector('main') !== null, title: document.title, width: window.innerWidth })`, + returnByValue: true, + })) as { result?: { value?: string } } + const raw = result.result?.value + if (typeof raw === 'string') { + const state = JSON.parse(raw) as BrowserSmokeState + lastObservedState = state + if (isBrowserSmokeReady(state, applicationTitle, readyText, viewport)) { + mounted = true + break + } + } + await Bun.sleep(100) + } + if (!mounted) + issues.push({ kind: 'mount', detail: `The ${appId} application root did not reach its expected ${applicationTitle}${readyText === undefined ? '' : ` / ${readyText}`} state within ${(mountTimeoutMilliseconds / 1000).toFixed(0)}s at ${pageUrl}. Last observed state: ${JSON.stringify(lastObservedState)}` }) + + if (mounted) { + const settleDeadline = Date.now() + Math.min(5_000, mountTimeoutMilliseconds) + while (Date.now() < settleDeadline && (Date.now() - session.getLastNetworkActivity() < 500 || ((options.requireWorker ?? true) && !session.hasWorkerStarted()))) await Bun.sleep(100) + if ((options.requireWorker ?? true) && !session.hasWorkerStarted()) issues.push({ kind: 'worker', detail: `No worker initialized for ${appId} at ${pageUrl}` }) + } + + const screenshotPath = process.env['UI_SCREENSHOT_PATH'] + if (screenshotPath !== undefined && screenshotPath !== '') { + const scrollSelector = process.env['UI_SCREENSHOT_SCROLL_SELECTOR'] + if (scrollSelector !== undefined && scrollSelector !== '') { + await send('Runtime.evaluate', { expression: `document.querySelector(${JSON.stringify(scrollSelector)})?.scrollIntoView({ block: 'start' })` }) + await Bun.sleep(100) + } + const result = (await send('Page.captureScreenshot', { captureBeyondViewport: false, format: 'png', fromSurface: true })) as { data?: unknown } + if (typeof result.data !== 'string') throw new Error('Chromium did not return PNG screenshot data.') + await fs.mkdir(path.dirname(screenshotPath), { recursive: true }) + await fs.writeFile(screenshotPath, Buffer.from(result.data, 'base64')) + } + + if (issues.length > 0) { + const summary = issues.map(issue => ` - [${issue.kind}] ${issue.detail}`).join('\n') + throw new Error(`Browser smoke check failed for ${appId} at ${pageUrl}:\n${summary}`) + } + console.log(`[browser-smoke] ${appId} mounted cleanly at ${pageUrl} (${viewport.width.toString()}x${viewport.height.toString()})`) + } finally { + await session.close() + } +} + +export async function runBrowserSmoke(appId: UiAppId, baseUrl: string, options: { readonly mountTimeoutMilliseconds?: number; readonly requireWorker?: boolean } = {}) { + await withChromiumTestLock(async () => await runBrowserSmokeUnlocked(appId, baseUrl, options)) +} + +async function main() { + const appId = parseUiAppIdFromProcess('the browser smoke check') + const paths = getUiAppPaths(appId) + void paths + const ports: Record = { statoblast: 12347, trading: 4163, zoltar: 12346 } + const explicitBaseUrl = process.env['UI_DEV_SERVER_URL'] + if (appId !== undefined && explicitBaseUrl === undefined) { + throw new Error(`Set UI_DEV_SERVER_URL to the running ${appId} dev server base URL (expected http://localhost:${ports[appId]} from bun run app:serve:${appId}).`) + } + await runBrowserSmoke(appId, explicitBaseUrl ?? `http://localhost:${ports[appId]}`) +} + +if (import.meta.main) { + main().catch(error => { + console.error(error instanceof Error ? error.message : String(error)) + process.exit(1) + }) +} diff --git a/ui/coreShared/build/browserSmoke.test.ts b/ui/coreShared/build/browserSmoke.test.ts new file mode 100644 index 000000000..3b43ad1f5 --- /dev/null +++ b/ui/coreShared/build/browserSmoke.test.ts @@ -0,0 +1,285 @@ +import { afterAll, beforeAll, describe, expect, test } from 'bun:test' +import { spawn } from 'node:child_process' +import { chmod, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises' +import { createServer, type Socket } from 'node:net' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { getChromiumPath, withChromiumTestLock } from './chromiumPath.js' +import { createBrowserSmokeCommandSender, createDevToolsSession, isBrowserSmokeReady, runBrowserSmoke, terminateBrowserProcess, waitForBrowserExit, waitForDevToolsPort } from './browserSmoke.mts' + +const mountedState = { + body: 'Augur Statoblast\nSecurity Pools', + height: 844, + hasMain: true, + title: 'Security Pools | Augur Statoblast', + width: 390, +} +const viewport = { height: 844, width: 390 } + +const getAvailablePort = async (): Promise => { + const server = createServer() + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(0, '127.0.0.1', () => resolve()) + }) + const address = server.address() + if (address === null || typeof address === 'string') throw new Error('Expected the test server to use a TCP port') + await new Promise((resolve, reject) => server.close(error => (error === undefined ? resolve() : reject(error)))) + return address.port +} + +test('browser smoke readiness requires the selected application identity', () => { + expect(isBrowserSmokeReady(mountedState, 'Augur Statoblast', undefined, viewport)).toBe(true) + expect(isBrowserSmokeReady({ ...mountedState, body: 'Security Pools' }, 'Augur Statoblast', undefined, viewport)).toBe(false) +}) + +test('browser smoke readiness can wait for route-specific loaded content', () => { + expect(isBrowserSmokeReady(mountedState, 'Augur Statoblast', 'Open pool', viewport)).toBe(false) + expect(isBrowserSmokeReady({ ...mountedState, body: `${mountedState.body}\nOpen pool` }, 'Augur Statoblast', 'Open pool', viewport)).toBe(true) +}) + +test('an explicit route-ready marker overrides stale bootstrap copy outside the route', () => { + const routeReadyState = { ...mountedState, body: `${mountedState.body}\nBOOTSTRAPPING\nDEPLOYMENT COMPLETE` } + expect(isBrowserSmokeReady(routeReadyState, 'Augur Statoblast', 'Deployment complete', viewport)).toBe(true) + expect(isBrowserSmokeReady(routeReadyState, 'Augur Statoblast', undefined, viewport)).toBe(false) +}) + +test('browser smoke readiness requires the exact requested CSS viewport', () => { + expect(isBrowserSmokeReady({ ...mountedState, width: 500 }, 'Augur Statoblast', undefined, viewport)).toBe(false) + expect(isBrowserSmokeReady({ ...mountedState, height: 701 }, 'Augur Statoblast', undefined, viewport)).toBe(false) +}) + +test('browser cleanup handles a Chromium process that already exited', async () => { + const browser = spawn(process.execPath, ['--eval', '']) + await new Promise((resolve, reject) => { + browser.once('error', reject) + browser.once('exit', () => resolve()) + }) + await expect(waitForBrowserExit(browser)).resolves.toBeUndefined() +}) + +test('browser commands reject when Chromium exits after the DevTools socket opens', async () => { + const browser = spawn(process.execPath, ['--eval', 'setInterval(() => {}, 1_000)']) + const socket = Object.assign(new EventTarget(), { send: () => undefined }) + const send = createBrowserSmokeCommandSender(socket, browser, 1_000) + const command = send('Runtime.evaluate') + browser.kill() + await expect(command).rejects.toThrow(/Chromium exited/) + await waitForBrowserExit(browser) +}) + +test('browser commands reject when the DevTools socket closes', async () => { + const browser = spawn(process.execPath, ['--eval', 'setInterval(() => {}, 1_000)']) + const socket = Object.assign(new EventTarget(), { send: () => undefined }) + const send = createBrowserSmokeCommandSender(socket, browser, 1_000) + const command = send('Runtime.evaluate') + socket.dispatchEvent(new Event('close')) + await expect(command).rejects.toThrow(/connection closed/) + browser.kill() + await waitForBrowserExit(browser) +}) + +test('browser commands bound a stalled DevTools response', async () => { + const browser = spawn(process.execPath, ['--eval', 'setInterval(() => {}, 1_000)']) + const socket = Object.assign(new EventTarget(), { send: () => undefined }) + const send = createBrowserSmokeCommandSender(socket, browser, 5) + await expect(send('Runtime.evaluate')).rejects.toThrow(/did not complete within 5ms/) + browser.kill() + await waitForBrowserExit(browser) +}) + +const listBrowserProfiles = async () => (await readdir(tmpdir())).filter(entry => entry.startsWith('zoltar-browser-smoke-')) + +test('browser launch failure removes the temporary profile', async () => { + const profilesBefore = new Set(await listBrowserProfiles()) + await expect(createDevToolsSession(join(tmpdir(), `missing-chromium-${crypto.randomUUID()}`), 'http://127.0.0.1', viewport, { pollMilliseconds: 1 })).rejects.toThrow(/launch Chromium|ENOENT/) + expect((await listBrowserProfiles()).filter(entry => !profilesBefore.has(entry))).toEqual([]) +}) + +test.skipIf(process.platform === 'win32')('browser cleanup escalates when Chromium ignores SIGTERM', async () => { + const profilePath = await mkdtemp(join(tmpdir(), 'zoltar-browser-smoke-')) + const browser = spawn(process.execPath, ['--eval', "process.on('SIGTERM', () => {}); console.log('ready'); setInterval(() => {}, 1_000)"]) + await new Promise((resolve, reject) => { + browser.once('error', reject) + browser.stdout.once('data', () => resolve()) + }) + const pid = browser.pid + if (pid === undefined) throw new Error('Expected the cleanup fixture to have a process ID') + await expect(terminateBrowserProcess(browser, profilePath, { forceTimeoutMilliseconds: 1_000, gracefulTimeoutMilliseconds: 10 })).resolves.toBeUndefined() + expect(() => process.kill(pid, 0)).toThrow() + expect(await Bun.file(profilePath).exists()).toBe(false) + await expect(withChromiumTestLock(async () => 'released', { port: await getAvailablePort() })).resolves.toBe('released') +}) + +test.skipIf(process.platform === 'win32')('browser cleanup escalates when sending SIGTERM emits an error', async () => { + const profilePath = await mkdtemp(join(tmpdir(), 'zoltar-browser-smoke-')) + const browser = spawn(process.execPath, ['--eval', "console.log('ready'); setInterval(() => {}, 1_000)"]) + await new Promise((resolve, reject) => { + browser.once('error', reject) + browser.stdout.once('data', () => resolve()) + }) + const originalKill = browser.kill.bind(browser) + const signals: Array = [] + browser.kill = signal => { + signals.push(signal) + if (signal === 'SIGTERM') { + queueMicrotask(() => browser.emit('error', new Error('simulated SIGTERM failure'))) + return false + } + return originalKill(signal) + } + await expect(terminateBrowserProcess(browser, profilePath, { forceTimeoutMilliseconds: 1_000, gracefulTimeoutMilliseconds: 10 })).resolves.toBeUndefined() + expect(signals).toEqual(['SIGTERM', 'SIGKILL']) + expect(await Bun.file(profilePath).exists()).toBe(false) +}) + +test.skipIf(process.platform === 'win32')('stalled DevTools discovery times out and reaps Chromium', async () => { + const fixtureRoot = await mkdtemp(join(tmpdir(), 'zoltar-browser-stall-fixture-')) + const executablePath = join(fixtureRoot, 'fake-chromium') + const pidPath = join(fixtureRoot, 'pid') + const sockets = new Set() + const server = createServer(socket => { + sockets.add(socket) + socket.once('close', () => sockets.delete(socket)) + }) + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(0, '127.0.0.1', () => resolve()) + }) + const address = server.address() + if (address === null || typeof address === 'string') throw new Error('Expected the DevTools stall fixture to use a TCP port') + const profilesBefore = new Set(await listBrowserProfiles()) + await writeFile(executablePath, `#!/bin/sh\nprofile=''\nfor argument in "$@"; do\n case "$argument" in\n --user-data-dir=*) profile="${'${argument#*=}'}" ;;\n esac\ndone\nprintf '${address.port.toString()}\\n' > "$profile/DevToolsActivePort"\nprintf '%s\\n' "$$" > ${JSON.stringify(pidPath)}\nexec sleep 60\n`) + await chmod(executablePath, 0o755) + try { + await expect(createDevToolsSession(executablePath, 'http://127.0.0.1', viewport, { initializationTimeoutMilliseconds: 2_000, pollMilliseconds: 1 })).rejects.toThrow(/timed out/) + const pid = Number((await readFile(pidPath, 'utf8')).trim()) + expect(() => process.kill(pid, 0)).toThrow() + expect((await listBrowserProfiles()).filter(entry => !profilesBefore.has(entry))).toEqual([]) + } finally { + for (const socket of sockets) socket.destroy() + await new Promise((resolve, reject) => server.close(error => (error === undefined ? resolve() : reject(error)))) + await rm(fixtureRoot, { force: true, recursive: true }) + } +}) + +const createFakeDevToolsServer = (emptyTargetResponses: number) => { + let targetRequests = 0 + const server = Bun.serve({ + hostname: '127.0.0.1', + port: 0, + fetch(request, bunServer) { + if (new URL(request.url).pathname === '/ws' && bunServer.upgrade(request)) return undefined + targetRequests += 1 + const targets = targetRequests <= emptyTargetResponses ? [] : [{ type: 'page', webSocketDebuggerUrl: `ws://127.0.0.1:${server.port.toString()}/ws` }] + return Response.json(targets) + }, + websocket: { message: () => undefined }, + }) + return server +} + +const writeFakeChromium = async (executablePath: string, devToolsPort: number, portDelaySeconds: string) => { + await writeFile(executablePath, `#!/bin/sh\nprofile=''\nfor argument in "$@"; do\n case "$argument" in\n --user-data-dir=*) profile="${'${argument#*=}'}" ;;\n esac\ndone\nsleep ${portDelaySeconds}\nprintf '${devToolsPort.toString()}\\n' > "$profile/DevToolsActivePort"\nexec sleep 60\n`) + await chmod(executablePath, 0o755) +} + +test('default DevTools port polling continues beyond the former 300-attempt limit', async () => { + let probes = 0 + const port = await waitForDevToolsPort({ + assertBrowserAvailable: () => undefined, + pollMilliseconds: 0, + readPort: async () => { + probes += 1 + return probes === 301 ? 9222 : undefined + }, + wait: async () => undefined, + }) + expect(port).toBe(9222) + expect(probes).toBe(301) +}) + +test.skipIf(process.platform === 'win32')('default initialization waits beyond the former page target retry limit', async () => { + const fixtureRoot = await mkdtemp(join(tmpdir(), 'zoltar-browser-delayed-target-')) + const executablePath = join(fixtureRoot, 'fake-chromium') + const server = createFakeDevToolsServer(220) + await writeFakeChromium(executablePath, server.port, '0') + try { + const session = await createDevToolsSession(executablePath, 'http://127.0.0.1', viewport, { initializationTimeoutMilliseconds: 5_000, pollMilliseconds: 1 }) + await session.close() + } finally { + server.stop(true) + await rm(fixtureRoot, { force: true, recursive: true }) + } +}) + +test.skipIf(process.platform === 'win32')('page target deadline identifies the phase that timed out', async () => { + const fixtureRoot = await mkdtemp(join(tmpdir(), 'zoltar-browser-target-timeout-')) + const executablePath = join(fixtureRoot, 'fake-chromium') + const server = createFakeDevToolsServer(Number.MAX_SAFE_INTEGER) + await writeFakeChromium(executablePath, server.port, '0') + try { + await expect(createDevToolsSession(executablePath, 'http://127.0.0.1', viewport, { initializationTimeoutMilliseconds: 2_000, pollMilliseconds: 1 })).rejects.toThrow(/timed out while waiting for the Chromium page target/) + } finally { + server.stop(true) + await rm(fixtureRoot, { force: true, recursive: true }) + } +}) + +test.skipIf(process.platform === 'win32')('browser initialization failure reaps Chromium and removes its profile', async () => { + const fixtureRoot = await mkdtemp(join(tmpdir(), 'zoltar-browser-fixture-')) + const executablePath = join(fixtureRoot, 'fake-chromium') + const pidPath = join(fixtureRoot, 'pid') + const profilesBefore = new Set(await listBrowserProfiles()) + await writeFile(executablePath, `#!/bin/sh\nprofile=''\nfor argument in "$@"; do\n case "$argument" in\n --user-data-dir=*) profile="${'${argument#*=}'}" ;;\n esac\ndone\nprintf '9\\n' > "$profile/DevToolsActivePort"\nprintf '%s\\n' "$$" > ${JSON.stringify(pidPath)}\nexec sleep 60\n`) + await chmod(executablePath, 0o755) + try { + await expect(createDevToolsSession(executablePath, 'http://127.0.0.1', viewport, { pollMilliseconds: 1, targetAttempts: 1 })).rejects.toThrow(/connect/i) + const pid = Number((await readFile(pidPath, 'utf8')).trim()) + expect(() => process.kill(pid, 0)).toThrow() + const profilesAfter = await listBrowserProfiles() + expect(profilesAfter.filter(entry => !profilesBefore.has(entry))).toEqual([]) + } finally { + await rm(fixtureRoot, { force: true, recursive: true }) + } +}) + +const chromiumPath = getChromiumPath() + +describe.skipIf(chromiumPath === undefined)('browser smoke failure integration', () => { + let fixture: ReturnType + let mode: 'import-map' | 'module' | 'worker' = 'import-map' + + beforeAll(() => { + fixture = Bun.serve({ + hostname: '127.0.0.1', + port: 0, + fetch(request) { + const pathname = new URL(request.url).pathname + if (pathname === '/working-worker.js') return new Response('self.postMessage("ready")', { headers: { 'Content-Type': 'text/javascript' } }) + if (pathname === '/missing-module.js' || pathname === '/missing-worker.js') return new Response('missing', { status: 404 }) + const workerScript = mode === 'worker' ? '/missing-worker.js' : '/working-worker.js' + const importMap = mode === 'import-map' ? '' : '' + const missingModule = mode === 'module' ? '' : '' + return new Response(`Zoltar
Zoltar
${importMap}${missingModule}`, { headers: { 'Content-Type': 'text/html' } }) + }, + }) + }) + + afterAll(() => fixture.stop(true)) + + test('reports malformed import maps', async () => { + mode = 'import-map' + await expect(runBrowserSmoke('zoltar', fixture.url.origin, { mountTimeoutMilliseconds: 5_000 })).rejects.toThrow(/\[import-map\]/) + }) + + test('reports HTTP failures for application modules', async () => { + mode = 'module' + await expect(runBrowserSmoke('zoltar', fixture.url.origin, { mountTimeoutMilliseconds: 5_000 })).rejects.toThrow(/\[request-failed\].*404.*missing-module\.js/s) + }) + + test('reports HTTP failures for worker entry points', async () => { + mode = 'worker' + await expect(runBrowserSmoke('zoltar', fixture.url.origin, { mountTimeoutMilliseconds: 5_000 })).rejects.toThrow(/\[(?:request-failed|worker)\].*missing-worker\.js/s) + }) +}) diff --git a/ui/coreShared/build/buildDag.test.ts b/ui/coreShared/build/buildDag.test.ts new file mode 100644 index 000000000..ac8ac33c9 --- /dev/null +++ b/ui/coreShared/build/buildDag.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, test } from 'bun:test' +import * as fs from 'node:fs' +import { getUiAppDependencyOrder, getUiCoreSharedPaths } from './appPaths.mts' + +type PackageJson = { + scripts?: Record +} + +function readRootPackageJson(): PackageJson { + const { repositoryRoot } = getUiCoreSharedPaths() + return JSON.parse(fs.readFileSync(`${repositoryRoot}/package.json`, 'utf8')) as PackageJson +} + +describe('UI build dependency direction', () => { + test('ui:build:apps compiles coreShared before the dependent apps', () => { + const scripts = readRootPackageJson().scripts ?? {} + const buildAppsScript = scripts['ui:build:apps'] + if (buildAppsScript === undefined) throw new Error('ui:build:apps script is missing') + const coreSharedIndex = buildAppsScript.indexOf('coreShared') + const zoltarIndex = buildAppsScript.indexOf('zoltar && bun run build') + const statoblastIndex = buildAppsScript.indexOf('statoblast && bun run build') + expect(coreSharedIndex).toBeGreaterThanOrEqual(0) + expect(zoltarIndex).toBeGreaterThan(coreSharedIndex) + expect(statoblastIndex).toBeGreaterThan(zoltarIndex) + const tradingIndex = buildAppsScript.indexOf('trading && bun run build') + expect(tradingIndex).toBeGreaterThan(statoblastIndex) + }) + + test('app serve/watch scripts build the full DAG before starting', () => { + const scripts = readRootPackageJson().scripts ?? {} + for (const name of ['app:serve:zoltar', 'app:serve:statoblast', 'app:serve:trading', 'app:watch:zoltar', 'app:watch:statoblast', 'app:watch:trading']) { + const script = scripts[name] + if (script === undefined) throw new Error(`${name} script is missing`) + expect(script.startsWith('bun run app:build')).toBe(true) + } + const appBuild = scripts['app:build'] + if (appBuild === undefined) throw new Error('app:build script is missing') + expect(appBuild).toContain('ui:build:apps') + }) + + test('the public production build emits the complete UI DAG before bundling', () => { + const scripts = readRootPackageJson().scripts ?? {} + const productionBuild = scripts['ui:build:prod'] + if (productionBuild === undefined) throw new Error('ui:build:prod script is missing') + const generateIndex = productionBuild.indexOf('bun run generate') + const appBuildIndex = productionBuild.indexOf('bun run ui:build:apps') + const productionBundleIndex = productionBuild.indexOf('bun run ui:build:prod:current') + expect(generateIndex).toBeGreaterThanOrEqual(0) + expect(appBuildIndex).toBeGreaterThan(generateIndex) + expect(productionBundleIndex).toBeGreaterThan(appBuildIndex) + }) + + test('setup scripts emit the complete UI DAG before compiling tests', () => { + const scripts = readRootPackageJson().scripts ?? {} + for (const name of ['setup', 'ui:setup']) { + const script = scripts[name] + if (script === undefined) throw new Error(`${name} script is missing`) + const appsIndex = script.indexOf('bun run ui:build:apps') + const testsIndex = script.indexOf('bun run ui:build:tests') + const tradingUiInstallIndex = script.indexOf('install-frozen.mts ui/trading') + expect(script).not.toContain('install-frozen.mts trading') + expect(tradingUiInstallIndex).toBeGreaterThan(0) + expect(appsIndex).toBeGreaterThan(0) + expect(appsIndex).toBeGreaterThan(tradingUiInstallIndex) + expect(testsIndex).toBeGreaterThan(appsIndex) + expect(script).not.toContain('cd ui/coreShared && bun x tsc') + } + }) + + test('package dependency direction stays coreShared <- zoltar <- statoblast <- trading', () => { + const { uiRoot } = getUiCoreSharedPaths() + const coreSharedPackage = JSON.parse(fs.readFileSync(`${uiRoot}/coreShared/package.json`, 'utf8')) as { dependencies?: Record } + const zoltarPackage = JSON.parse(fs.readFileSync(`${uiRoot}/zoltar/package.json`, 'utf8')) as { dependencies?: Record } + const statoblastPackage = JSON.parse(fs.readFileSync(`${uiRoot}/statoblast/package.json`, 'utf8')) as { dependencies?: Record } + const tradingPackage = JSON.parse(fs.readFileSync(`${uiRoot}/trading/package.json`, 'utf8')) as { dependencies?: Record } + + for (const dependency of ['@zoltar/ui-zoltar', '@zoltar/ui-statoblast', '@zoltar/ui-trading']) expect(coreSharedPackage.dependencies?.[dependency]).toBeUndefined() + expect(zoltarPackage.dependencies?.['@zoltar/ui-core-shared']).toBeDefined() + for (const dependency of ['@zoltar/ui-statoblast', '@zoltar/ui-trading']) expect(zoltarPackage.dependencies?.[dependency]).toBeUndefined() + expect(statoblastPackage.dependencies?.['@zoltar/ui-core-shared']).toBeDefined() + expect(statoblastPackage.dependencies?.['@zoltar/ui-zoltar']).toBeDefined() + expect(statoblastPackage.dependencies?.['@zoltar/ui-trading']).toBeUndefined() + expect(tradingPackage.dependencies?.['@zoltar/ui-core-shared']).toBeDefined() + expect(tradingPackage.dependencies?.['@zoltar/ui-zoltar']).toBeDefined() + expect(tradingPackage.dependencies?.['@zoltar/ui-statoblast']).toBeDefined() + expect(tradingPackage.dependencies?.['@zoltar/trading']).toBeUndefined() + }) + + test('watch mode starts every TypeScript project required by the selected app', () => { + expect(getUiAppDependencyOrder('zoltar')).toEqual(['coreShared', 'zoltar']) + expect(getUiAppDependencyOrder('statoblast')).toEqual(['coreShared', 'zoltar', 'statoblast']) + expect(getUiAppDependencyOrder('trading')).toEqual(['coreShared', 'zoltar', 'statoblast', 'trading']) + }) + + test('Trading watch mode rebuilds shared SDK and main contract outputs and reloads app CSS', () => { + const { coreSharedRoot } = getUiCoreSharedPaths() + const watchSource = fs.readFileSync(`${coreSharedRoot}/build/watch.mts`, 'utf8') + expect(watchSource).toContain("path.join(REPOSITORY_ROOT_PATH, 'shared', 'ts')") + expect(watchSource).toContain("path.join(REPOSITORY_ROOT_PATH, 'solidity', 'contracts')") + expect(watchSource).toContain("spawn(BUN_EXECUTABLE_PATH, ['run', 'generate:contracts']") + expect(watchSource).toContain("path.join(APP_ROOT_PATH, 'css')") + expect(watchSource).not.toContain('TRADING_PACKAGE_ROOT_PATH') + }) + + test('ui:build:tests compiles each package test tree exactly once', () => { + const scripts = readRootPackageJson().scripts ?? {} + const buildTestsScript = scripts['ui:build:tests'] + if (buildTestsScript === undefined) throw new Error('ui:build:tests script is missing') + expect(buildTestsScript.match(/bun run build:tests/g)).toHaveLength(4) + expect(buildTestsScript).toContain('cd ui/coreShared') + expect(buildTestsScript).toContain('cd ../zoltar') + expect(buildTestsScript).toContain('cd ../statoblast') + expect(buildTestsScript).toContain('cd ../trading') + }) +}) diff --git a/ui/build/bundlerPaths.mts b/ui/coreShared/build/bundlerPaths.mts similarity index 100% rename from ui/build/bundlerPaths.mts rename to ui/coreShared/build/bundlerPaths.mts diff --git a/ui/build/bundlerPaths.test.ts b/ui/coreShared/build/bundlerPaths.test.ts similarity index 100% rename from ui/build/bundlerPaths.test.ts rename to ui/coreShared/build/bundlerPaths.test.ts diff --git a/ui/build/chromiumPath.test.ts b/ui/coreShared/build/chromiumPath.test.ts similarity index 100% rename from ui/build/chromiumPath.test.ts rename to ui/coreShared/build/chromiumPath.test.ts diff --git a/ui/build/chromiumPath.ts b/ui/coreShared/build/chromiumPath.ts similarity index 100% rename from ui/build/chromiumPath.ts rename to ui/coreShared/build/chromiumPath.ts diff --git a/ui/build/dockerServe.mts b/ui/coreShared/build/dockerServe.mts similarity index 77% rename from ui/build/dockerServe.mts rename to ui/coreShared/build/dockerServe.mts index ef5fbecfd..ea58d6dab 100644 --- a/ui/build/dockerServe.mts +++ b/ui/coreShared/build/dockerServe.mts @@ -1,4 +1,5 @@ import path from 'node:path' +import { isUiAppId, UI_APP_IDS } from './appPaths.mts' const noStoreFiles = new Set(['/index.html', '/mainnet-deployment-addresses.json', '/sepolia-deployment-addresses.json']) @@ -38,11 +39,15 @@ export const createAssetHandler = (root: string) => { } if (import.meta.main) { + const appId = process.argv[2] ?? process.env['UI_APP'] + if (appId === undefined || appId === '') throw new Error(`Missing UI app ID for the local runtime server; pass one of: ${UI_APP_IDS.join(', ')}`) + if (!isUiAppId(appId)) throw new Error(`Unknown UI app ID '${appId}' for the local runtime server; expected one of: ${UI_APP_IDS.join(', ')}`) const configuredPort = process.env['PORT'] ?? '8080' const port = Number(configuredPort) if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error(`Invalid PORT value: ${configuredPort}`) - Bun.serve({ hostname: '0.0.0.0', port, fetch: createAssetHandler('/app/ui') }) - console.log('Zoltar UI is available at:') + const appDistRoot = `/app/ui/${appId}` + Bun.serve({ hostname: '0.0.0.0', port, fetch: createAssetHandler(appDistRoot) }) + console.log(`${appId} UI is available at:`) console.log(`http://localhost:${port}/`) } diff --git a/ui/build/dockerServe.test.ts b/ui/coreShared/build/dockerServe.test.ts similarity index 90% rename from ui/build/dockerServe.test.ts rename to ui/coreShared/build/dockerServe.test.ts index 6b9289366..ec839c937 100644 --- a/ui/build/dockerServe.test.ts +++ b/ui/coreShared/build/dockerServe.test.ts @@ -2,6 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from 'bun:test' import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import path from 'node:path' +import { isUiAppId, UI_APP_IDS } from './appPaths.mts' import { createAssetHandler } from './dockerServe.mts' let fixtureRoot = '' @@ -69,4 +70,12 @@ describe('Docker static UI server', () => { await server.stop(true) } }) + + test('app selection accepts every app ID and rejects unknown IDs', () => { + expect(UI_APP_IDS).toEqual(['zoltar', 'statoblast', 'trading']) + expect(isUiAppId('zoltar')).toBe(true) + expect(isUiAppId('statoblast')).toBe(true) + expect(isUiAppId('monolith')).toBe(false) + expect(isUiAppId('')).toBe(false) + }) }) diff --git a/ui/coreShared/build/importMaps.test.ts b/ui/coreShared/build/importMaps.test.ts new file mode 100644 index 000000000..55299890e --- /dev/null +++ b/ui/coreShared/build/importMaps.test.ts @@ -0,0 +1,63 @@ +import { expect, test } from 'bun:test' +import * as fs from 'node:fs' +import { UI_APP_IDS, getUiAppPaths } from './appPaths.mts' + +type ImportMapFile = { + imports?: Record +} + +const IMPORT_MAP_SCRIPT_PATTERN = /]*>([\s\S]*?)<\/script>/g + +function readImportMaps(indexHtmlPath: string) { + const indexHtml = fs.readFileSync(indexHtmlPath, 'utf8') + const importMaps: Record[] = [] + for (const match of indexHtml.matchAll(IMPORT_MAP_SCRIPT_PATTERN)) { + const scriptBody = match[1] + if (scriptBody === undefined) continue + let importMap: ImportMapFile + try { + importMap = JSON.parse(scriptBody) as ImportMapFile + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + throw new Error(`Failed to parse the import map in ${indexHtmlPath} as JSON: ${message}`) + } + if (importMap.imports === undefined) throw new Error(`Expected the import map in ${indexHtmlPath} to include imports.`) + importMaps.push(importMap.imports) + } + if (importMaps.length === 0) throw new Error(`Expected ${indexHtmlPath} to contain a development import map script.`) + return importMaps +} + +for (const appId of UI_APP_IDS) { + const paths = getUiAppPaths(appId) + + test(`${appId}/index.html import maps are valid JSON with required mappings`, () => { + const importMaps = readImportMaps(paths.appIndexHtml) + expect(importMaps).toHaveLength(1) + const imports = importMaps[0] + if (imports === undefined) throw new Error('unreachable') + + expect(imports['preact']).toBe('./vendor/preact/preact.module.js') + expect(imports['preact/hooks']).toBe('./vendor/preact/hooks/hooks.module.js') + expect(imports['@preact/signals']).toBe('./vendor/@preact/signals/signals.module.js') + expect(imports['@zoltar/shared/bigInt']).toBe('../shared/js/bigInt.js') + expect(imports['@zoltar/shared/ethereum']).toBe('../shared/js/ethereum.js') + expect(imports['tevm']).toBe('./vendor/tevm/index.js') + expect(imports['@zoltar/ui-core-shared/']).toBe('/ui/coreShared/js/') + if (appId === 'statoblast' || appId === 'trading') { + expect(imports['@zoltar/ui-zoltar/']).toBe('/ui/zoltar/js/') + } else { + expect(imports['@zoltar/ui-zoltar/']).toBeUndefined() + } + if (appId === 'trading') { + expect(imports['@zoltar/ui-statoblast/']).toBe('/ui/statoblast/js/') + expect(imports['@zoltar/shared/trading/math']).toBe('../shared/js/trading/math.js') + expect(imports['@zoltar/shared/trading/positions']).toBe('../shared/js/trading/positions.js') + expect(imports['@zoltar/shared/trading/transactions']).toBe('../shared/js/trading/transactions.js') + } + + for (const mappedPath of Object.values(imports)) { + expect(mappedPath.endsWith(',') || mappedPath.endsWith('}')).toBe(false) + } + }) +} diff --git a/ui/build/index.production.html b/ui/coreShared/build/index.production.html similarity index 100% rename from ui/build/index.production.html rename to ui/coreShared/build/index.production.html diff --git a/ui/coreShared/build/ipfsPublisher.test.ts b/ui/coreShared/build/ipfsPublisher.test.ts new file mode 100644 index 000000000..742687ec9 --- /dev/null +++ b/ui/coreShared/build/ipfsPublisher.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, test } from 'bun:test' +import * as fs from 'node:fs' +import * as path from 'node:path' +import { UI_APP_IDS, getUiAppPaths, getUiCoreSharedPaths } from './appPaths.mts' + +const { repositoryRoot } = getUiCoreSharedPaths() +const dockerfilePath = path.join(repositoryRoot, 'ui', 'Dockerfile') + +const PUBLISHER_COPY_PATTERN = /^COPY --from=builder (\/source\/\S+?) (\/export\/\S+)$/gm + +type PublisherExport = { + readonly sourcePath: string + readonly exportPath: string +} + +function readPublisherExports(): PublisherExport[] { + const dockerfile = fs.readFileSync(dockerfilePath, 'utf8') + const exports: PublisherExport[] = [] + for (const match of dockerfile.matchAll(PUBLISHER_COPY_PATTERN)) { + const sourcePath = match[1] + const exportPath = match[2] + if (sourcePath === undefined || exportPath === undefined) continue + exports.push({ sourcePath, exportPath }) + } + return exports +} + +const HTML_REFERENCE_PATTERN = /(?:src|href)=["']([^"']+)["']/g + +function readHtmlReferences(indexHtmlPath: string) { + const html = fs.readFileSync(indexHtmlPath, 'utf8') + const references: string[] = [] + for (const match of html.matchAll(HTML_REFERENCE_PATTERN)) { + const reference = match[1] + if (reference === undefined) continue + if (reference.startsWith('http://') || reference.startsWith('https://') || reference.startsWith('data:') || reference.startsWith('#')) continue + references.push(reference.replace(/^\.\//, '').replace(/^\//, '')) + } + return references +} + +describe('IPFS publisher layout', () => { + test('every app has an advertised export path in the publisher target', () => { + const exports = readPublisherExports() + const exportRoots = new Set(exports.map(entry => entry.exportPath.split('/').slice(0, 3).join('/'))) + for (const appId of UI_APP_IDS) { + expect(exportRoots.has(`/export/${appId}`)).toBe(true) + } + }) + + test('every advertised export path has an index.html and all referenced local assets', () => { + const exports = readPublisherExports() + for (const appId of UI_APP_IDS) { + const appPaths = getUiAppPaths(appId) + const advertisedRoot = `/export/${appId}` + const advertisedExports = exports.filter(entry => entry.exportPath === advertisedRoot || entry.exportPath.startsWith(`${advertisedRoot}/`)) + expect(advertisedExports.length).toBeGreaterThan(0) + + // Map /export//... back onto the builder dist tree to prove the exported tree is servable. + const exportedFiles = new Set() + for (const entry of advertisedExports) { + const sourceRelative = entry.sourcePath.replace('/source/', '') + const exportRelative = entry.exportPath.replace(`${advertisedRoot}/`, '') + const sourceAbsolute = path.join(repositoryRoot, sourceRelative) + if (entry.sourcePath.endsWith('/')) { + // Directory copy: exportRelative is the directory name. + expect(fs.existsSync(sourceAbsolute)).toBe(true) + exportedFiles.add(exportRelative.replace(/\/$/, '')) + continue + } + expect(fs.existsSync(sourceAbsolute)).toBe(true) + exportedFiles.add(exportRelative) + } + + expect(exportedFiles.has('index.html')).toBe(true) + const distIndexHtml = path.join(appPaths.appDistRoot, 'index.html') + expect(fs.existsSync(distIndexHtml)).toBe(true) + for (const reference of readHtmlReferences(distIndexHtml)) { + const topLevelDirectory = reference.split('/')[0] + expect(exportedFiles.has(reference) || exportedFiles.has(reference.replace(/\/[^/]*$/, '')) || exportedFiles.has(topLevelDirectory ?? reference)).toBe(true) + } + } + }) +}) diff --git a/ui/build/production.mts b/ui/coreShared/build/production.mts similarity index 51% rename from ui/build/production.mts rename to ui/coreShared/build/production.mts index 9e58f7983..9f4408a96 100644 --- a/ui/build/production.mts +++ b/ui/coreShared/build/production.mts @@ -1,13 +1,17 @@ import { promises as fs } from 'fs' import * as path from 'path' import * as process from 'node:process' -import * as url from 'node:url' import { normalizeBundlerPath, resolveBundlerSpecifierPath } from './bundlerPaths.mts' +import { parseUiAppIdFromProcess, getUiAppPaths, type UiAppPaths } from './appPaths.mts' + +const appId = parseUiAppIdFromProcess('the production build') +const paths = getUiAppPaths(appId) + +// Bun records source paths relative to the current working directory in bundle +// comments and source maps. Normalize it so root and package scripts produce +// byte-identical deployable artifacts. +process.chdir(paths.appRoot) -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const UI_ROOT_PATH = path.join(directoryOfThisFile, '..') -const DIST_ROOT_PATH = path.join(UI_ROOT_PATH, 'dist') -const DIST_ASSETS_PATH = path.join(DIST_ROOT_PATH, 'assets') const WORKER_BANNER = ` const process = globalThis.process ?? { env: {}, @@ -23,6 +27,12 @@ globalThis.process ??= process globalThis.global ??= globalThis `.trim() +const APP_TITLES: Record = { + zoltar: 'Zoltar', + statoblast: 'Augur Statoblast', + trading: 'Statoblast trading', +} + function createBrowserVendorAliasPlugin() { const aliasEntries: Array<[RegExp, string]> = [ [/^pino$/, resolveBundlerSpecifierPath('pino/browser.js')], @@ -54,39 +64,50 @@ async function copyStaticAsset(sourcePath: string, destinationPath: string) { await Bun.write(destinationPath, await sourceFile.arrayBuffer()) } -async function writeProductionIndexHtml() { - const templatePath = path.join(UI_ROOT_PATH, 'build', 'index.production.html') - const html = await fs.readFile(templatePath, 'utf8') - await fs.mkdir(DIST_ROOT_PATH, { recursive: true }) - await fs.writeFile(path.join(DIST_ROOT_PATH, 'index.html'), html) +function assertBuildSucceeded(label: string, result: { success: boolean; logs: Array }) { + if (result.success) return + const messages = result.logs.map(log => (typeof log === 'object' && log !== null && 'message' in log ? String(log.message) : String(log))).join('\n') + throw new Error(`${label} failed for ${appId}\n${messages}`) } -async function buildProductionApp() { - await Bun.build({ - entrypoints: [normalizeBundlerPath(path.join(UI_ROOT_PATH, 'ts', 'index.ts'))], +async function writeProductionIndexHtml(paths: UiAppPaths) { + const templatePath = path.join(paths.coreSharedRoot, 'build', 'index.production.html') + let html = await fs.readFile(templatePath, 'utf8') + const appTitle = APP_TITLES[appId] + if (appTitle === undefined) throw new Error(`No production title recorded for ${appId}`) + html = html.replace('Zoltar + Augur Statoblast', appTitle) + if (appId === 'trading') html = html.replace('', '\n\t\t') + await fs.mkdir(paths.appDistRoot, { recursive: true }) + await fs.writeFile(path.join(paths.appDistRoot, 'index.html'), html) +} + +async function buildProductionApp(paths: UiAppPaths) { + const result = await Bun.build({ + entrypoints: [normalizeBundlerPath(paths.appEntrypoint)], naming: { entry: 'app.js', chunk: 'chunks/[name]-[hash].js', }, - outdir: DIST_ASSETS_PATH, + outdir: paths.appDistAssetsRoot, plugins: [createBrowserVendorAliasPlugin()], target: 'browser', sourcemap: 'linked', }) + assertBuildSucceeded('Production application bundle', result) } -async function buildProductionWorker() { - const workerEntryPath = path.join(UI_ROOT_PATH, 'ts', 'simulation', 'tevmWorker.ts') +async function buildProductionWorker(paths: UiAppPaths) { const BANNER_LINE_COUNT = WORKER_BANNER.split('\n').length const result = await Bun.build({ - entrypoints: [normalizeBundlerPath(workerEntryPath)], + entrypoints: [normalizeBundlerPath(paths.workerEntrypoint)], naming: { entry: 'tevmWorker.worker.js' }, - outdir: DIST_ASSETS_PATH, + outdir: paths.appDistAssetsRoot, plugins: [createBrowserVendorAliasPlugin()], target: 'browser', sourcemap: 'linked', }) + assertBuildSucceeded('Production worker bundle', result) for (const output of result.outputs) { if (output.path.endsWith('.js')) { @@ -125,17 +146,27 @@ async function buildProductionWorker() { } export async function buildProductionBundle() { - await fs.rm(DIST_ROOT_PATH, { recursive: true, force: true }) - await fs.mkdir(DIST_ASSETS_PATH, { recursive: true }) + await fs.rm(paths.appDistRoot, { recursive: true, force: true }) + await fs.mkdir(paths.appDistAssetsRoot, { recursive: true }) await Promise.all([ - buildProductionApp(), - buildProductionWorker(), - writeProductionIndexHtml(), - copyStaticAsset(path.join(UI_ROOT_PATH, 'css', 'index.css'), path.join(DIST_ROOT_PATH, 'css', 'index.css')), - copyStaticAsset(path.join(UI_ROOT_PATH, 'css', 'tokens.css'), path.join(DIST_ROOT_PATH, 'css', 'tokens.css')), - copyStaticAsset(path.join(UI_ROOT_PATH, 'favicon.ico'), path.join(DIST_ROOT_PATH, 'favicon.ico')), - copyStaticAsset(path.join(UI_ROOT_PATH, 'favicon.svg'), path.join(DIST_ROOT_PATH, 'favicon.svg')), + buildProductionApp(paths), + buildProductionWorker(paths), + writeProductionIndexHtml(paths), + copyStaticAsset(path.join(paths.coreSharedCssRoot, 'index.css'), path.join(paths.appDistRoot, 'css', 'index.css')), + copyStaticAsset(path.join(paths.coreSharedCssRoot, 'tokens.css'), path.join(paths.appDistRoot, 'css', 'tokens.css')), + copyStaticAsset(paths.faviconIco, path.join(paths.appDistRoot, 'favicon.ico')), + copyStaticAsset(paths.faviconSvg, path.join(paths.appDistRoot, 'favicon.svg')), + ...(appId === 'trading' + ? [ + copyStaticAsset(path.join(paths.appRoot, 'css', 'app.css'), path.join(paths.appDistRoot, 'css', 'app.css')), + import(path.join(paths.appRoot, 'build', 'core-deployments.mts')).then(async module => { + const writer = module['writeCoreDeploymentRegistry'] + if (typeof writer !== 'function') throw new Error('Trading core deployment registry writer is missing') + await writer(path.join(paths.appDistRoot, 'core-deployments.json')) + }), + ] + : []), ]) } diff --git a/ui/build/productionBuild.test.ts b/ui/coreShared/build/productionBuild.test.ts similarity index 82% rename from ui/build/productionBuild.test.ts rename to ui/coreShared/build/productionBuild.test.ts index cdef87729..cc544c907 100644 --- a/ui/build/productionBuild.test.ts +++ b/ui/coreShared/build/productionBuild.test.ts @@ -3,106 +3,168 @@ import * as fs from 'node:fs/promises' import * as os from 'node:os' import * as path from 'node:path' import * as process from 'node:process' -import * as url from 'node:url' import { getChromiumPath, withChromiumTestLock } from './chromiumPath.js' +import { UI_APP_IDS, getUiAppPaths, getUiCoreSharedPaths, isUiAppId, type UiAppId } from './appPaths.mts' -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const repositoryRootPath = path.join(directoryOfThisFile, '..', '..') -const distRootPath = path.join(repositoryRootPath, 'ui', 'dist') -const distAssetsPath = path.join(distRootPath, 'assets') -const appBundlePath = path.join(distAssetsPath, 'app.js') -const appSourceMapPath = path.join(distAssetsPath, 'app.js.map') -const workerBundlePath = path.join(distAssetsPath, 'tevmWorker.worker.js') -const workerSourceMapPath = path.join(distAssetsPath, 'tevmWorker.worker.js.map') -const productionIndexPath = path.join(distRootPath, 'index.html') -const productionCssPath = path.join(distRootPath, 'css', 'index.css') -const productionTokensCssPath = path.join(distRootPath, 'css', 'tokens.css') -const productionFaviconPaths = [path.join(distRootPath, 'favicon.ico'), path.join(distRootPath, 'favicon.svg')] +const appPathsById = new Map(UI_APP_IDS.map(appId => [appId, getUiAppPaths(appId)])) +const repositoryRootPath = getUiCoreSharedPaths().repositoryRoot const CHROMIUM_STARTUP_TIMEOUT_MILLISECONDS = 30_000 const CHROMIUM_DEVTOOLS_PROBE_TIMEOUT_MILLISECONDS = 1_000 +const PRODUCTION_BROWSER_TIMEOUT_MILLISECONDS = 120_000 const PRODUCTION_WORKFLOW_TIMEOUT_MILLISECONDS = 600_000 let server: Bun.Server | undefined const chromiumPath = getChromiumPath() -const productionBrowserTest = test +const productionBrowserTest = (name: string, run: () => Promise) => test(name, run, PRODUCTION_BROWSER_TIMEOUT_MILLISECONDS) const productionWorkflowTest = (name: string, run: () => Promise) => test(name, run, PRODUCTION_WORKFLOW_TIMEOUT_MILLISECONDS) beforeAll(async () => { if (process.env['ZOLTAR_USE_EXISTING_PRODUCTION_BUILD'] !== '1') { - const result = Bun.spawnSync([process.execPath, 'run', 'ui:build:prod'], { + const result = Bun.spawnSync([process.execPath, 'run', 'ui:build:prod:current'], { cwd: repositoryRootPath, stderr: 'pipe', stdout: 'pipe', }) if (result.exitCode !== 0) { - throw new Error(`ui:build:prod failed\n${new TextDecoder().decode(result.stdout)}${new TextDecoder().decode(result.stderr)}`) + throw new Error(`ui:build:prod:current failed\n${new TextDecoder().decode(result.stdout)}${new TextDecoder().decode(result.stderr)}`) } } server = Bun.serve({ fetch: async request => { const requestUrl = new URL(request.url) - const relativePath = requestUrl.pathname === '/' ? 'index.html' : requestUrl.pathname.replace(/^\/+/, '') - const filePath = path.join(distRootPath, relativePath) + // The CI prepare job uploads the app dist directories preserving their full + // ui//dist layout, so serve both layouts: /zoltar/... and /ui/zoltar/dist/... + // resolve to the app dist root, and unprefixed / falls back to zoltar. + const uiLayoutMatch = /^\/ui\/([a-z]+)\/dist(\/.*)?$/.exec(requestUrl.pathname) + const uiLayoutAppId = uiLayoutMatch?.[1] + const pathname = uiLayoutMatch?.[2] ?? requestUrl.pathname + const matchedAppId = uiLayoutAppId !== undefined && isUiAppId(uiLayoutAppId) ? uiLayoutAppId : (UI_APP_IDS.find(appId => pathname === `/${appId}` || pathname.startsWith(`/${appId}/`)) ?? (pathname === '/' || pathname.startsWith('/assets/') || pathname.startsWith('/css/') ? 'zoltar' : undefined)) + if (matchedAppId === undefined) { + return new Response('not found', { status: 404 }) + } + const appPaths = appPathsById.get(matchedAppId) + if (appPaths === undefined) throw new Error(`No path information recorded for ${matchedAppId}.`) + let appRelativePath = pathname + if (pathname === `/${matchedAppId}` || pathname === '/') appRelativePath = '/' + else if (pathname.startsWith(`/${matchedAppId}/`)) appRelativePath = pathname.slice(`/${matchedAppId}`.length) + const relativePath = appRelativePath === '/' ? 'index.html' : appRelativePath.replace(/^\/+/, '') + const filePath = path.join(appPaths.appDistRoot, relativePath) + if (!filePath.startsWith(`${appPaths.appDistRoot}${path.sep}`)) { + return new Response('forbidden', { status: 403 }) + } const file = Bun.file(filePath) if (!(await file.exists())) { return new Response('not found', { status: 404 }) } return new Response(file) }, + hostname: '127.0.0.1', port: 0, }) -}) +}, PRODUCTION_WORKFLOW_TIMEOUT_MILLISECONDS) afterAll(() => { server?.stop(true) }) -test('production build emits the deployable artifact set', async () => { - const expectedPaths = [productionIndexPath, productionCssPath, productionTokensCssPath, appBundlePath, appSourceMapPath, workerBundlePath, workerSourceMapPath, ...productionFaviconPaths] +for (const appId of UI_APP_IDS) { + const appPaths = appPathsById.get(appId) + if (appPaths === undefined) throw new Error(`No path information recorded for ${appId}.`) + const distRootPath = appPaths.appDistRoot + const distAssetsPath = appPaths.appDistAssetsRoot + const appBundlePath = path.join(distAssetsPath, 'app.js') + const appSourceMapPath = path.join(distAssetsPath, 'app.js.map') + const workerBundlePath = path.join(distAssetsPath, 'tevmWorker.worker.js') + const workerSourceMapPath = path.join(distAssetsPath, 'tevmWorker.worker.js.map') + const productionIndexPath = path.join(distRootPath, 'index.html') + const productionCssPath = path.join(distRootPath, 'css', 'index.css') + const productionTokensCssPath = path.join(distRootPath, 'css', 'tokens.css') + const productionFaviconPaths = [path.join(distRootPath, 'favicon.ico'), path.join(distRootPath, 'favicon.svg')] + const expectedTitles: Record = { statoblast: 'Augur Statoblast', trading: 'Statoblast trading', zoltar: 'Zoltar' } + const expectedTitle = expectedTitles[appId] + const otherTitles = ['Zoltar', 'Augur Statoblast', 'Statoblast trading'].filter(title => title !== expectedTitle) + const expectedRoutes: Record = { statoblast: '#/security-pools', trading: '#/markets', zoltar: '#/zoltar' } + const expectedRoute = expectedRoutes[appId] + + test(`${appId} production build emits the deployable artifact set`, async () => { + const expectedPaths = [productionIndexPath, productionCssPath, productionTokensCssPath, appBundlePath, appSourceMapPath, workerBundlePath, workerSourceMapPath, ...productionFaviconPaths, ...(appId === 'trading' ? [path.join(distRootPath, 'css', 'app.css'), path.join(distRootPath, 'core-deployments.json')] : [])] + + for (const expectedPath of expectedPaths) { + await expect(fs.access(expectedPath)).resolves.toBeNull() + } + }) - for (const expectedPath of expectedPaths) { - await expect(fs.access(expectedPath)).resolves.toBeNull() - } -}) + test(`${appId} production index html references the bundled app and does not use the dev import map`, async () => { + const html = await fs.readFile(productionIndexPath, 'utf8') -test('production index html references the bundled app and does not use the dev import map', async () => { - const html = await fs.readFile(productionIndexPath, 'utf8') + expect(html).toMatch(/\s*<\/script>/) + expect(html).not.toContain('importmap') + expect(html).not.toContain('./js/') + expect(html).not.toContain('./vendor/') + expect(html).toContain(`${expectedTitle}`) + for (const otherTitle of otherTitles) expect(html).not.toContain(`${otherTitle}`) + }) - expect(html).toMatch(/\s*<\/script>/) - expect(html).not.toContain('importmap') - expect(html).not.toContain('./js/') - expect(html).not.toContain('./vendor/') -}) + test(`${appId} production javascript is self-contained for deploys`, async () => { + const appBundle = await fs.readFile(appBundlePath, 'utf8') + const workerBundle = await fs.readFile(workerBundlePath, 'utf8') + const appSourceMap = JSON.parse(await fs.readFile(appSourceMapPath, 'utf8')) as { sources: string[] } + const workerSourceMap = JSON.parse(await fs.readFile(workerSourceMapPath, 'utf8')) as { sources: string[] } + + expect(appBundle).not.toContain('./vendor/') + expect(appBundle).not.toContain('./js/') + expect(appBundle).toContain('new URL("./tevmWorker.worker.js", import.meta.url)') + expect(appBundle).toContain('"/assets/"') + expect(workerBundle).not.toContain('./vendor/') + expect(workerBundle).not.toContain('./js/') + expect(appSourceMap.sources.some(source => source.replaceAll('\\', '/').startsWith('../../ts/'))).toBe(true) + expect(workerSourceMap.sources.some(source => source.replaceAll('\\', '/').startsWith('../../ts/'))).toBe(true) + expect(appBundle).toContain(expectedRoute) + if (appId === 'trading') { + expect(appBundle).not.toContain('SIMULATED DATA') + expect(appBundle).not.toContain('Demo mode') + expect(appBundle).not.toContain('demo-banner') + } + }) -test('production javascript is self-contained for deploys', async () => { - const appBundle = await fs.readFile(appBundlePath, 'utf8') - const workerBundle = await fs.readFile(workerBundlePath, 'utf8') - const appSourceMap = JSON.parse(await fs.readFile(appSourceMapPath, 'utf8')) as { sources: string[] } - const workerSourceMap = JSON.parse(await fs.readFile(workerSourceMapPath, 'utf8')) as { sources: string[] } - - expect(appBundle).not.toContain('./vendor/') - expect(appBundle).not.toContain('./js/') - expect(appBundle).toContain('new URL("./tevmWorker.worker.js", import.meta.url)') - expect(workerBundle).not.toContain('./vendor/') - expect(workerBundle).not.toContain('./js/') - expect(appSourceMap.sources.some(source => source.replaceAll('\\', '/').startsWith('../../ts/'))).toBe(true) - expect(workerSourceMap.sources.some(source => source.replaceAll('\\', '/').startsWith('../../ts/'))).toBe(true) -}) + test(`${appId} production build can be served as static files`, async () => { + if (server === undefined) { + throw new Error('Production test server did not start') + } -test('production build can be served as static files', async () => { - if (server === undefined) { - throw new Error('Production test server did not start') - } + const baseUrl = server.url.toString().replace(/\/$/, '') + const responses = await Promise.all([fetch(`${baseUrl}/${appId}/`), fetch(`${baseUrl}/${appId}/assets/app.js`), fetch(`${baseUrl}/${appId}/assets/tevmWorker.worker.js`), fetch(`${baseUrl}/${appId}/css/index.css`), fetch(`${baseUrl}/${appId}/css/tokens.css`)]) - const baseUrl = server.url.toString().replace(/\/$/, '') - const responses = await Promise.all([fetch(`${baseUrl}/`), fetch(`${baseUrl}/assets/app.js`), fetch(`${baseUrl}/assets/tevmWorker.worker.js`), fetch(`${baseUrl}/css/index.css`), fetch(`${baseUrl}/css/tokens.css`)]) + for (const response of responses) { + expect(response.status).toBe(200) + } + }) - for (const response of responses) { - expect(response.status).toBe(200) - } -}) + test(`building ${appId} does not remove the other app's production output`, async () => { + const otherAppId = appId === 'zoltar' ? 'statoblast' : 'zoltar' + const otherPaths = appPathsById.get(otherAppId) + if (otherPaths === undefined) throw new Error(`No path information recorded for ${otherAppId}.`) + const otherIndexPath = path.join(otherPaths.appDistRoot, 'index.html') + const otherHtml = await fs.readFile(otherIndexPath, 'utf8') + expect(otherHtml).toContain('') + expect(distRootPath).not.toBe(otherPaths.appDistRoot) + }) + + test(`${appId} production output is independent of the invoking working directory`, async () => { + const rootBuild = await fs.readFile(appBundlePath) + const result = Bun.spawnSync([process.execPath, appPaths.productionBuildScript, appId], { + cwd: appPaths.appRoot, + stderr: 'pipe', + stdout: 'pipe', + }) + if (result.exitCode !== 0) { + throw new Error(`production build from ${appPaths.appRoot} failed\n${new TextDecoder().decode(result.stdout)}${new TextDecoder().decode(result.stderr)}`) + } + expect(await fs.readFile(appBundlePath)).toEqual(rootBuild) + }) +} type BrowserProcessStatus = Pick<Bun.Subprocess, 'exitCode' | 'signalCode'> type BrowserProcess = Pick<Bun.Subprocess, 'exitCode' | 'exited' | 'signalCode'> @@ -161,7 +223,7 @@ test('Chromium DevTools port discovery tolerates delayed startup', async () => { await writePortFile await fs.rm(profilePath, { force: true, recursive: true }) } -}) +}, 10_000) test('Chromium DevTools port discovery reports an early browser exit', async () => { await expect(waitForDevToolsPort('/missing/chromium/DevToolsActivePort', { exitCode: 17, signalCode: null })).rejects.toThrow('Chromium exited with code 17 before publishing its DevTools port') @@ -255,7 +317,7 @@ test('Chromium page target discovery tolerates a delayed initial page target', a } finally { devToolsServer.stop(true) } -}) +}, 10_000) test('Chromium page target discovery retries after a stalled target response', async () => { let requestCount = 0 @@ -674,27 +736,38 @@ async function loadProductionDocumentInChromium(pageUrl: string, viewport: { hei const productionBrowserScenarios = [ { + appId: 'zoltar', hash: '#/deploy?simulate=1&simScenario=baseline', expected: 'Deploy Contracts', - name: 'baseline deployment', + name: 'zoltar baseline deployment', viewport: { height: 900, width: 1440 }, }, { + appId: 'zoltar', hash: '#/zoltar?simulate=1&simScenario=deployed', expected: 'Questions', - name: 'deployed protocol', + name: 'zoltar deployed protocol', viewport: { height: 900, width: 1440 }, }, { + appId: 'statoblast', + hash: '#/deploy?simulate=1&simScenario=baseline', + expected: 'Deploy Contracts', + name: 'statoblast baseline deployment', + viewport: { height: 900, width: 1440 }, + }, + { + appId: 'statoblast', hash: '#/security-pools?simulate=1&simScenario=security-pool', expected: 'Security Pools', - name: 'seeded pool at narrow width', + name: 'statoblast seeded pool at narrow width', viewport: { height: 844, width: 390 }, }, { + appId: 'statoblast', hash: '#/security-pools?simulate=1&simScenario=securitypoolx2-auction', expected: 'Truth Auction', - name: 'fork and auction', + name: 'statoblast fork and auction', viewport: { height: 900, width: 1440 }, }, ] as const @@ -704,12 +777,13 @@ for (const scenario of productionBrowserScenarios) { if (server === undefined) throw new Error('Production test server did not start') if (chromiumPath === undefined) throw new Error('Chromium is required for the production browser smoke test') const baseUrl = server.url.toString().replace(/\/$/, '') - const state = JSON.parse(await loadProductionDocumentInChromium(`${baseUrl}/${scenario.hash}`, scenario.viewport)) + const state = JSON.parse(await loadProductionDocumentInChromium(`${baseUrl}/${scenario.appId}/${scenario.hash}`, scenario.viewport)) if (typeof state !== 'object' || state === null || !('body' in state) || !('html' in state) || typeof state.body !== 'string' || typeof state.html !== 'string' || !('height' in state) || !('width' in state)) { throw new Error(`${scenario.name} returned an invalid document state`) } expect(state.html).toContain('<main') expect(state.body).toContain(scenario.expected) + expect(state.body).toContain(scenario.appId === 'zoltar' ? 'Zoltar' : 'Augur Statoblast') expect(state.body).toContain('Simulation') expect(state.height).toBe(scenario.viewport.height) expect(state.width).toBe(scenario.viewport.width) @@ -722,7 +796,7 @@ productionWorkflowTest('production bundle executes deployment, reporting, fork m if (chromiumPath === undefined) throw new Error('Chromium is required for the production browser workflow test') const baseUrl = server.url.toString().replace(/\/$/, '') const state = JSON.parse( - await loadProductionDocumentInChromium(`${baseUrl}/#/deploy?simulate=1&simScenario=baseline`, { height: 900, width: 1440 }, async driver => { + await loadProductionDocumentInChromium(`${baseUrl}/statoblast/#/deploy?simulate=1&simScenario=baseline`, { height: 900, width: 1440 }, async driver => { await driver.evaluate('document.body.focus()') await driver.pressTab() expect(await driver.evaluate('document.activeElement?.textContent?.trim()')).toBe('Skip to main content') @@ -732,7 +806,7 @@ productionWorkflowTest('production bundle executes deployment, reporting, fork m expect(deployedBody).not.toContain('Failed to initialize the app environment') await driver.resize({ height: 844, width: 390 }) - await driver.navigate(`${baseUrl}/?workflow=pool#/security-pools?simulate=1&simScenario=security-pool`) + await driver.navigate(`${baseUrl}/statoblast/?workflow=pool#/security-pools?simulate=1&simScenario=security-pool`) await driver.waitForBodyText('Open pool') await driver.clickButton('Open pool') await driver.waitForBodyWithoutText('Loading vault…') @@ -763,7 +837,7 @@ productionWorkflowTest('production bundle executes deployment, reporting, fork m expect(poolBody).toContain('Manage Pool') await driver.resize({ height: 900, width: 1440 }) - await driver.navigate(`${baseUrl}/?workflow=reporting#/security-pools?simulate=1&simScenario=securitypoolx2`) + await driver.navigate(`${baseUrl}/statoblast/?workflow=reporting#/security-pools?simulate=1&simScenario=securitypoolx2`) await driver.waitForBodyText('Open pool') await driver.clickButton('+1 year') const reportingPoolOpened = await driver.evaluate( @@ -841,29 +915,7 @@ productionWorkflowTest('production bundle executes deployment, reporting, fork m await driver.clickButton('Open fork & migration') await driver.waitForBodyText('Fork & Migration') - const zoltarOpened = await driver.evaluate(`(() => { const target = [...document.querySelectorAll('a, button')].find(candidate => candidate.textContent?.trim() === 'Zoltar'); if (!(target instanceof HTMLElement)) return false; target.click(); return true })()`) - expect(zoltarOpened).toBe(true) - const missingUniverseOpened = await driver.evaluate( - `(() => { const [route, search = ''] = window.location.hash.split('?'); const params = new URLSearchParams(search); params.set('universe', '999999'); window.history.pushState({}, '', route + '?' + params.toString()); window.dispatchEvent(new PopStateEvent('popstate')); return true })()`, - ) - expect(missingUniverseOpened).toBe(true) - await driver.waitForButtonEnabled('Go to Genesis universe') - await driver.clickButton('Go to Genesis universe') - await driver.waitForBodyText('Forked') - await driver.waitForBodyText('Child Universes') - await driver.waitForButtonEnabled('Create child universe', 1) - await driver.clickButton('Create child universe', 1) - await driver.waitForBodyText('Create Child Universe') - await driver.waitForButtonEnabled('Deploy universe') - await driver.clickButton('Deploy universe') - await driver.waitForBodyText('Child Universe Deployed') - - const poolsOpened = await driver.evaluate(`(() => { const target = [...document.querySelectorAll('a, button')].find(candidate => candidate.textContent?.trim() === 'Security Pools'); if (!(target instanceof HTMLElement)) return false; target.click(); return true })()`) - expect(poolsOpened).toBe(true) - await driver.waitForButtonEnabled('Open pool') - await driver.clickButton('Open pool') - await driver.waitForButtonEnabled('Fork & Migration') - await driver.clickButton('Fork & Migration') + // The fork workflow view now owns the full migration flow; drive it directly. await driver.waitForButtonEnabled('Migrate pool to Yes universe') await driver.clickButton('Migrate pool to Yes universe') await driver.waitForBodyText('Pool-held REP was migrated into the selected child universe.') @@ -872,7 +924,7 @@ productionWorkflowTest('production bundle executes deployment, reporting, fork m await driver.waitForBodyText('Vault REP backing and capacity ownership were migrated into the selected child universe.') await driver.resize({ height: 900, width: 1440 }) - await driver.navigate(`${baseUrl}/?workflow=auction#/security-pools?simulate=1&simScenario=securitypoolx2-auction`) + await driver.navigate(`${baseUrl}/statoblast/?workflow=auction#/security-pools?simulate=1&simScenario=securitypoolx2-auction`) await driver.waitForBodyText('Open pool') await driver.clickButton('+1 month') const auctionPoolOpened = await driver.evaluate( diff --git a/ui/build/projectArtifacts.mts b/ui/coreShared/build/projectArtifacts.mts similarity index 76% rename from ui/build/projectArtifacts.mts rename to ui/coreShared/build/projectArtifacts.mts index cc1fc7148..68bccaf38 100644 --- a/ui/build/projectArtifacts.mts +++ b/ui/coreShared/build/projectArtifacts.mts @@ -5,11 +5,12 @@ import * as url from 'node:url' const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) const UI_ROOT_PATH = path.join(directoryOfThisFile, '..') -const REPOSITORY_ROOT_PATH = path.join(UI_ROOT_PATH, '..') +const REPOSITORY_ROOT_PATH = path.join(UI_ROOT_PATH, '..', '..') const ABI_OUTPUT_PATH = path.join(UI_ROOT_PATH, 'ts', 'abis.ts') const ABI_SOURCE_PATH = path.join(REPOSITORY_ROOT_PATH, 'solidity', 'ts', 'abi', 'abis.ts') const CONTRACT_ARTIFACT_OUTPUT_PATH = path.join(UI_ROOT_PATH, 'ts', 'contractArtifact.ts') const CONTRACT_ARTIFACTS_JSON_PATH = path.join(REPOSITORY_ROOT_PATH, 'solidity', 'artifacts', 'Contracts.json') +const TRADING_CONTRACT_ARTIFACT_OUTPUT_PATH = path.join(UI_ROOT_PATH, '..', 'trading', 'ts', 'generated', 'contractArtifact.ts') type CompiledContract = { readonly abi?: unknown @@ -42,6 +43,10 @@ export async function copyProjectArtifacts() { }) await fs.writeFile(CONTRACT_ARTIFACT_OUTPUT_PATH, `${contracts.join('\n\n')}\n`) + + const tradingContracts = Object.fromEntries(Object.entries(compiledArtifacts.contracts).filter(([filename]) => filename.startsWith('contracts/trading/'))) + await fs.mkdir(path.dirname(TRADING_CONTRACT_ARTIFACT_OUTPUT_PATH), { recursive: true }) + await fs.writeFile(TRADING_CONTRACT_ARTIFACT_OUTPUT_PATH, `// Generated from solidity/artifacts/Contracts.json by ui/coreShared/build/projectArtifacts.mts. Do not edit.\nexport const tradingContracts = ${JSON.stringify(tradingContracts)} as const\n`) } const currentScriptPath = url.fileURLToPath(import.meta.url) diff --git a/ui/build/sharedAssets.test.ts b/ui/coreShared/build/sharedAssets.test.ts similarity index 60% rename from ui/build/sharedAssets.test.ts rename to ui/coreShared/build/sharedAssets.test.ts index d02749e2b..8f16681e5 100644 --- a/ui/build/sharedAssets.test.ts +++ b/ui/coreShared/build/sharedAssets.test.ts @@ -2,25 +2,28 @@ import { expect, test } from 'bun:test' import * as fs from 'node:fs' import * as os from 'node:os' import * as path from 'node:path' -import * as url from 'node:url' import * as ts from 'typescript' -import { sharedBrowserArtifactRelativePaths } from '../../scripts/sharedBrowserArtifacts.ts' +import { sharedBrowserArtifactRelativePaths } from '../../../scripts/sharedBrowserArtifacts.ts' import { clearVendorOutput, vendor } from './vendor.mts' - -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const repositoryRootPath = path.join(directoryOfThisFile, '..', '..') -const uiRootPath = path.join(repositoryRootPath, 'ui') -const uiProtocolPaths = ['forks.ts', 'openOracle.ts', 'trading.ts'].map(fileName => path.join(repositoryRootPath, 'ui', 'ts', 'protocol', fileName)) -const uiDeploymentHelpersPath = path.join(repositoryRootPath, 'ui', 'ts', 'protocol', 'deploymentHelpers.ts') -const uiReportingDomainPath = path.join(repositoryRootPath, 'ui', 'ts', 'features', 'reporting', 'lib', 'reportingDomain.ts') -const uiSepoliaDeploymentConfigPath = path.join(repositoryRootPath, 'ui', 'ts', 'lib', 'sepoliaDeploymentConfig.ts') -const uiSimulationBootstrapPath = path.join(repositoryRootPath, 'ui', 'ts', 'simulation', 'bootstrap.ts') -const uiTruthAuctionBookPath = path.join(repositoryRootPath, 'ui', 'ts', 'features', 'truth-auctions', 'lib', 'truthAuctionBook.ts') -const uiIndexHtmlPath = path.join(repositoryRootPath, 'ui', 'index.html') -const uiVendorBuildPath = path.join(repositoryRootPath, 'ui', 'build', 'vendor.mts') -const uiWatchBuildPath = path.join(repositoryRootPath, 'ui', 'build', 'watch.mts') -const uiPackageJsonPath = path.join(repositoryRootPath, 'ui', 'package.json') -const uiDevelopmentEntrypointPath = path.join(repositoryRootPath, 'ui', 'ts', 'index.dev.ts') +import { UI_APP_IDS, getUiAppPaths, getUiCoreSharedPaths } from './appPaths.mts' + +const coreSharedPaths = getUiCoreSharedPaths() +const appPathsById = new Map(UI_APP_IDS.map(appId => [appId, getUiAppPaths(appId)])) +const zoltarPaths = appPathsById.get('zoltar') +const statoblastPaths = appPathsById.get('statoblast') +if (zoltarPaths === undefined || statoblastPaths === undefined) throw new Error('Failed to resolve the UI application paths.') +const repositoryRootPath = coreSharedPaths.repositoryRoot +const uiRootPath = coreSharedPaths.uiRoot +const uiProtocolPaths = [path.join(zoltarPaths.appSourceRoot, 'protocol', 'forks.ts'), path.join(zoltarPaths.appSourceRoot, 'protocol', 'openOracle.ts'), path.join(statoblastPaths.appSourceRoot, 'protocol', 'trading.ts')] +const uiDeploymentHelpersPath = path.join(zoltarPaths.appSourceRoot, 'protocol', 'deploymentHelpers.ts') +const uiReportingDomainPath = path.join(zoltarPaths.appSourceRoot, 'features', 'reporting', 'lib', 'reportingDomain.ts') +const uiSepoliaDeploymentConfigPath = path.join(coreSharedPaths.coreSharedSourceRoot, 'lib', 'sepoliaDeploymentConfig.ts') +const uiSimulationBootstrapPath = path.join(coreSharedPaths.coreSharedSourceRoot, 'simulation', 'bootstrap.ts') +const uiTruthAuctionBookPath = path.join(statoblastPaths.appSourceRoot, 'features', 'truth-auctions', 'lib', 'truthAuctionBook.ts') +const uiIndexHtmlPaths = new Map(UI_APP_IDS.map(appId => [appId, path.join(uiRootPath, appId, 'index.html')])) +const uiVendorBuildPath = path.join(coreSharedPaths.coreSharedRoot, 'build', 'vendor.mts') +const uiWatchBuildPath = path.join(coreSharedPaths.coreSharedRoot, 'build', 'watch.mts') +const rootPackageJsonPath = path.join(repositoryRootPath, 'package.json') const sharedBrowserArtifacts = sharedBrowserArtifactRelativePaths.map(relativePath => path.join(repositoryRootPath, relativePath)) const developmentImportMapRegressionEntries: Record<string, string> = { '@zoltar/shared/ethereum': '../shared/js/ethereum.js', @@ -57,15 +60,17 @@ type ResolvedImport = { kind: 'file' | 'vendor' } -function readDevelopmentImportMap(): Record<string, string> { +function readDevelopmentImportMap(appId: (typeof UI_APP_IDS)[number]): Record<string, string> { + const uiIndexHtmlPath = uiIndexHtmlPaths.get(appId) + if (uiIndexHtmlPath === undefined) throw new Error(`No index.html path recorded for ${appId}.`) const uiIndexHtml = fs.readFileSync(uiIndexHtmlPath, 'utf8') const importMapMatch = uiIndexHtml.match(/<script\s+type=['"]importmap['"][^>]*>([\s\S]*?)<\/script>/) if (importMapMatch === null || importMapMatch[1] === undefined) { - throw new Error('Expected ui/index.html to contain a development import map script.') + throw new Error(`Expected ${uiIndexHtmlPath} to contain a development import map script.`) } const importMap = JSON.parse(importMapMatch[1]) as ImportMapFile if (importMap.imports === undefined) { - throw new Error('Expected development import map to include imports.') + throw new Error(`Expected the development import map in ${uiIndexHtmlPath} to include imports.`) } return importMap.imports } @@ -91,7 +96,7 @@ function resolveImportMapSpecifier(specifier: string, imports: Record<string, st return `${matchedPath}${specifier.slice(matchedPrefix.length)}` } -function resolveDevelopmentImport(fromPath: string, specifier: string, imports: Record<string, string>) { +function resolveDevelopmentImport(fromPath: string, specifier: string, imports: Record<string, string>, appRootPath: string) { if (!isBareModuleSpecifier(specifier)) { const resolvedSourcePath = resolveSourceModulePath(fromPath, specifier) if (resolvedSourcePath !== undefined) { @@ -108,28 +113,43 @@ function resolveDevelopmentImport(fromPath: string, specifier: string, imports: } const mappedSpecifier = resolveImportMapSpecifier(specifier, imports) - if (mappedSpecifier === undefined) return undefined - if (mappedSpecifier.startsWith('./vendor/')) { + if (mappedSpecifier === undefined) { + const vendorFallback = resolveSourceModulePath(fromPath, path.join('vendor', specifier)) + if (vendorFallback !== undefined) return { filePath: vendorFallback, kind: 'vendor' } satisfies ResolvedImport + return undefined + } + let rawResolvedPath = path.resolve(appRootPath, mappedSpecifier) + if (mappedSpecifier.startsWith('/')) rawResolvedPath = path.join(repositoryRootPath, mappedSpecifier) + else if (mappedSpecifier.startsWith('../shared/')) rawResolvedPath = path.join(repositoryRootPath, mappedSpecifier.replace(/^\.\.\//, '')) + if (rawResolvedPath.includes(`${path.sep}shared${path.sep}js${path.sep}`)) { return { - filePath: path.resolve(uiRootPath, mappedSpecifier), - kind: 'vendor', + filePath: rawResolvedPath, + kind: 'file', + } satisfies ResolvedImport + } + const sourceResolvedPath = resolveSourceModulePath(fromPath, rawResolvedPath) + if (sourceResolvedPath !== undefined && !sourceResolvedPath.endsWith('.js') && !sourceResolvedPath.endsWith('.mjs')) { + return { + filePath: sourceResolvedPath, + kind: 'file', } satisfies ResolvedImport } - return { - filePath: path.resolve(uiRootPath, mappedSpecifier), - kind: 'file', + filePath: rawResolvedPath, + kind: mappedSpecifier.startsWith('./vendor/') ? 'vendor' : 'file', } satisfies ResolvedImport } function resolveSourceModulePath(fromPath: string, specifier: string) { const rawResolvedPath = path.resolve(path.dirname(fromPath), specifier) - const candidatePaths = [rawResolvedPath] if (rawResolvedPath.endsWith('.js')) { const withoutJavaScriptExtension = rawResolvedPath.slice(0, -'.js'.length) - candidatePaths.push(`${withoutJavaScriptExtension}.ts`, `${withoutJavaScriptExtension}.tsx`, `${withoutJavaScriptExtension}.mts`) + const siblingSourceCandidates = [`${withoutJavaScriptExtension}.ts`, `${withoutJavaScriptExtension}.tsx`, `${withoutJavaScriptExtension}.mts`] + const siblingSource = siblingSourceCandidates.find(candidatePath => fs.existsSync(candidatePath)) + if (siblingSource !== undefined) return siblingSource } - candidatePaths.push(path.join(rawResolvedPath, 'index.ts'), path.join(rawResolvedPath, 'index.tsx'), path.join(rawResolvedPath, 'index.js')) + const candidatePaths = [rawResolvedPath] + candidatePaths.push(`${rawResolvedPath}.js`, `${rawResolvedPath}.mjs`, path.join(rawResolvedPath, 'index.ts'), path.join(rawResolvedPath, 'index.tsx'), path.join(rawResolvedPath, 'index.js')) return candidatePaths.find(candidatePath => fs.existsSync(candidatePath)) } @@ -251,15 +271,17 @@ function collectBareBunStringLiterals(sourceFile: ts.SourceFile) { return locations } -function getExportedNames(filePath: string, imports: Record<string, string>, exportCache: Map<string, Set<string>>) { +function getExportedNames(filePath: string, imports: Record<string, string>, exportCache: Map<string, Set<string>>, appRootPath: string) { const cachedExports = exportCache.get(filePath) if (cachedExports !== undefined) return cachedExports const exportedNames = new Set<string>() exportCache.set(filePath, exportedNames) - if (!fs.existsSync(filePath)) return exportedNames + const sourceFilePath = resolveSourceModulePath(filePath, filePath) + const effectiveFilePath = sourceFilePath !== undefined && !sourceFilePath.endsWith('.js') && !sourceFilePath.endsWith('.mjs') ? sourceFilePath : filePath + if (!fs.existsSync(effectiveFilePath)) return exportedNames - const sourceFile = parseModule(filePath, fs.readFileSync(filePath, 'utf8')) + const sourceFile = parseModule(effectiveFilePath, fs.readFileSync(effectiveFilePath, 'utf8')) for (const statement of sourceFile.statements) { if (ts.isExportDeclaration(statement)) { if (!isRuntimeExportDeclaration(statement)) continue @@ -278,9 +300,9 @@ function getExportedNames(filePath: string, imports: Record<string, string>, exp const specifier = getModuleSpecifierText(statement) if (specifier === undefined) continue - const resolvedPath = resolveDevelopmentImport(filePath, specifier, imports) + const resolvedPath = resolveDevelopmentImport(filePath, specifier, imports, appRootPath) if (resolvedPath === undefined || !fs.existsSync(resolvedPath.filePath)) continue - for (const name of getExportedNames(resolvedPath.filePath, imports, exportCache)) { + for (const name of getExportedNames(resolvedPath.filePath, imports, exportCache, appRootPath)) { if (name !== 'default') exportedNames.add(name) } continue @@ -316,7 +338,7 @@ test('shared helper package imports resolve to browser-served shared outputs', ( const deploymentHelpersSource = fs.readFileSync(uiDeploymentHelpersPath, 'utf8') const sepoliaDeploymentConfigSource = fs.readFileSync(uiSepoliaDeploymentConfigPath, 'utf8') const simulationBootstrapSource = fs.readFileSync(uiSimulationBootstrapPath, 'utf8') - const uiIndexHtml = fs.readFileSync(uiIndexHtmlPath, 'utf8') + const appIndexHtmlSources = [...uiIndexHtmlPaths.values()].map(indexPath => fs.readFileSync(indexPath, 'utf8')) expect(protocolSource).toContain("from './helpers.js'") expect(protocolSource).toContain("from './deploymentHelpers.js'") @@ -331,20 +353,22 @@ test('shared helper package imports resolve to browser-served shared outputs', ( expect(protocolSource).not.toContain('./shared/bigInt.js') expect(simulationBootstrapSource).not.toContain('../shared/constants.js') expect(deploymentHelpersSource).not.toContain('../shared/deploymentAddresses.js') - expect(uiIndexHtml).toContain('"@zoltar/shared/bigInt": "../shared/js/bigInt.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/constants": "../shared/js/constants.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/deploymentAddresses": "../shared/js/deploymentAddresses.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/escalationMath": "../shared/js/escalationMath.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/ethereum": "../shared/js/ethereum.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/liquidation": "../shared/js/liquidation.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/openOracle": "../shared/js/openOracle.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/oracleInitialReport": "../shared/js/oracleInitialReport.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/protocolConfig": "../shared/js/protocolConfig.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/scalarOutcome": "../shared/js/scalarOutcome.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/sepoliaRepAllocations": "../shared/js/sepoliaRepAllocations.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/sortStringArrayByKeccak": "../shared/js/sortStringArrayByKeccak.js"') - expect(uiIndexHtml).toContain('"@zoltar/shared/truthAuctionTickMath": "../shared/js/truthAuctionTickMath.js"') - expect(uiIndexHtml).not.toContain('"viem": "./vendor/viem/index.js"') + for (const uiIndexHtml of appIndexHtmlSources) { + expect(uiIndexHtml).toContain('"@zoltar/shared/bigInt": "../shared/js/bigInt.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/constants": "../shared/js/constants.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/deploymentAddresses": "../shared/js/deploymentAddresses.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/escalationMath": "../shared/js/escalationMath.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/ethereum": "../shared/js/ethereum.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/liquidation": "../shared/js/liquidation.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/openOracle": "../shared/js/openOracle.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/oracleInitialReport": "../shared/js/oracleInitialReport.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/protocolConfig": "../shared/js/protocolConfig.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/scalarOutcome": "../shared/js/scalarOutcome.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/sepoliaRepAllocations": "../shared/js/sepoliaRepAllocations.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/sortStringArrayByKeccak": "../shared/js/sortStringArrayByKeccak.js"') + expect(uiIndexHtml).toContain('"@zoltar/shared/truthAuctionTickMath": "../shared/js/truthAuctionTickMath.js"') + expect(uiIndexHtml).not.toContain('"viem": "./vendor/viem/index.js"') + } expect(sharedBrowserArtifactRelativePaths).toContain('shared/js/scalarOutcome.js') for (const artifactPath of sharedBrowserArtifacts) { @@ -353,23 +377,27 @@ test('shared helper package imports resolve to browser-served shared outputs', ( }) test('watch build regression scanner catches indirect bare Bun commands', () => { - const fixtureSourceFile = parseModule(path.join(repositoryRootPath, 'ui', 'build', 'bare-bun-fixture.mts'), ["const BUN_COMMAND = 'bun'", "spawn(BUN_COMMAND, ['x', 'tsc'])", "runSharedBuildStep([BUN_COMMAND, 'run', 'shared:build'])"].join('\n')) + const fixtureSourceFile = parseModule(path.join(repositoryRootPath, 'ui', 'coreShared', 'build', 'bare-bun-fixture.mts'), ["const BUN_COMMAND = 'bun'", "spawn(BUN_COMMAND, ['x', 'tsc'])", "runSharedBuildStep([BUN_COMMAND, 'run', 'shared:build'])"].join('\n')) - expect(collectBareBunStringLiterals(fixtureSourceFile)).toEqual(['ui/build/bare-bun-fixture.mts:1:21']) + expect(collectBareBunStringLiterals(fixtureSourceFile)).toEqual(['ui/coreShared/build/bare-bun-fixture.mts:1:21']) }) test('development import map maps browser dependency subpaths', () => { - const imports = readDevelopmentImportMap() const vendorBuildSource = fs.readFileSync(uiVendorBuildPath, 'utf8') const watchBuildSource = fs.readFileSync(uiWatchBuildPath, 'utf8') const watchBuildSourceFile = parseModule(uiWatchBuildPath, watchBuildSource) - const uiPackageJson = JSON.parse(fs.readFileSync(uiPackageJsonPath, 'utf8')) as { scripts?: Record<string, string | undefined> } + const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf8')) as { scripts?: Record<string, string | undefined> } - for (const [specifier, mappedPath] of Object.entries(developmentImportMapRegressionEntries)) { - expect(imports[specifier]).toBe(mappedPath) + for (const appId of UI_APP_IDS) { + const imports = readDevelopmentImportMap(appId) + for (const [specifier, mappedPath] of Object.entries(developmentImportMapRegressionEntries)) { + expect(imports[specifier], `${appId} import map entry for ${specifier}`).toBe(mappedPath) + } } - expect(uiPackageJson.scripts?.watch).toStartWith('cd .. && bun run generate && cd ui') - expect(uiPackageJson.scripts?.serve).toStartWith('cd .. && bun run generate && cd ui') + expect(rootPackageJson.scripts?.['app:watch:zoltar']).toContain('build/watch.mts zoltar') + expect(rootPackageJson.scripts?.['app:watch:statoblast']).toContain('build/watch.mts statoblast') + expect(rootPackageJson.scripts?.['app:serve:zoltar']).toContain('dev-server.ts zoltar') + expect(rootPackageJson.scripts?.['app:serve:statoblast']).toContain('dev-server.ts statoblast') expect(vendorBuildSource).toContain("{ packageName: 'isows', subfolderToVendor: '_esm', mainEntrypointFile: 'native.js'") expect(watchBuildSource).toContain('const VENDOR_INPUT_PATHS = [VENDOR_BUILD_PATH, BUNDLER_PATHS_BUILD_PATH') expect(watchBuildSource).toContain('const WORKER_INPUT_PATHS = [WORKER_BUILD_PATH, BUNDLER_PATHS_BUILD_PATH]') @@ -410,52 +438,60 @@ test('vendor build clears generated output before rebuilding assets', async () = expect(completedSteps).toEqual(['clearVendorOutput', 'bundleTevm', 'vendorDependencies', 'copyProjectArtifacts']) }) -test('development import map resolves all static imports reachable from the dev entrypoint', () => { - const imports = readDevelopmentImportMap() - const pendingPaths = [uiDevelopmentEntrypointPath] - const visitedPaths = new Set<string>() - const exportCache = new Map<string, Set<string>>() - const unresolvedImports: string[] = [] - - while (pendingPaths.length > 0) { - const currentPath = pendingPaths.pop() - if (currentPath === undefined || visitedPaths.has(currentPath)) continue - visitedPaths.add(currentPath) - - if (!fs.existsSync(currentPath)) { - unresolvedImports.push(`Missing module ${path.relative(repositoryRootPath, currentPath)}`) - continue - } - - const source = fs.readFileSync(currentPath, 'utf8') - const sourceFile = parseModule(currentPath, source) - for (const specifier of collectRuntimeModuleSpecifiers(sourceFile)) { - const resolvedPath = resolveDevelopmentImport(currentPath, specifier, imports) - if (resolvedPath === undefined) { - unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports unmapped bare specifier ${specifier}`) - continue +for (const appId of UI_APP_IDS) { + test(`development import map resolves all static imports reachable from the ${appId} dev entrypoint`, () => { + const appPaths = appPathsById.get(appId) + if (appPaths === undefined) throw new Error(`No path information recorded for ${appId}.`) + const imports = readDevelopmentImportMap(appId) + const pendingPaths = [path.join(appPaths.appSourceRoot, 'index.dev.ts')] + const visitedPaths = new Set<string>() + const exportCache = new Map<string, Set<string>>() + const unresolvedImports: string[] = [] + + while (pendingPaths.length > 0) { + const currentPath = pendingPaths.pop() + if (currentPath === undefined) continue + const withinSharedGeneratedOutput = currentPath.includes(`${path.sep}shared${path.sep}js${path.sep}`) + if (!withinSharedGeneratedOutput) { + if (visitedPaths.has(currentPath)) continue + visitedPaths.add(currentPath) } - if (!fs.existsSync(resolvedPath.filePath)) { - unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports ${specifier}, but ${path.relative(repositoryRootPath, resolvedPath.filePath)} does not exist`) + + if (!fs.existsSync(currentPath)) { + unresolvedImports.push(`Missing module ${path.relative(repositoryRootPath, currentPath)}`) continue } - pendingPaths.push(resolvedPath.filePath) - } - for (const reference of collectNamedModuleReferences(sourceFile)) { - const resolvedPath = resolveDevelopmentImport(currentPath, reference.specifier, imports) - if (resolvedPath === undefined) { - unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports named bindings from unmapped bare specifier ${reference.specifier}`) - continue + const source = fs.readFileSync(currentPath, 'utf8') + const sourceFile = parseModule(currentPath, source) + for (const specifier of collectRuntimeModuleSpecifiers(sourceFile)) { + const resolvedPath = resolveDevelopmentImport(currentPath, specifier, imports, appPaths.appRoot) + if (resolvedPath === undefined) { + unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports unmapped bare specifier ${specifier}`) + continue + } + if (!fs.existsSync(resolvedPath.filePath)) { + unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports ${specifier}, but ${path.relative(repositoryRootPath, resolvedPath.filePath)} does not exist`) + continue + } + pendingPaths.push(resolvedPath.filePath) } - if (!fs.existsSync(resolvedPath.filePath)) continue - const exportedNames = getExportedNames(resolvedPath.filePath, imports, exportCache) - for (const name of reference.names) { - if (exportedNames.has(name)) continue - unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports ${name} from ${reference.specifier}, but ${path.relative(repositoryRootPath, resolvedPath.filePath)} does not export it`) + + for (const reference of collectNamedModuleReferences(sourceFile)) { + const resolvedPath = resolveDevelopmentImport(currentPath, reference.specifier, imports, appPaths.appRoot) + if (resolvedPath === undefined) { + unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports named bindings from unmapped bare specifier ${reference.specifier}`) + continue + } + if (!fs.existsSync(resolvedPath.filePath)) continue + const exportedNames = getExportedNames(resolvedPath.filePath, imports, exportCache, appPaths.appRoot) + for (const name of reference.names) { + if (exportedNames.has(name)) continue + unresolvedImports.push(`${path.relative(repositoryRootPath, currentPath)} imports ${name} from ${reference.specifier}, but ${path.relative(repositoryRootPath, resolvedPath.filePath)} does not export it`) + } } } - } - expect(unresolvedImports).toEqual([]) -}) + expect(unresolvedImports).toEqual([]) + }) +} diff --git a/ui/coreShared/build/tests.mts b/ui/coreShared/build/tests.mts new file mode 100644 index 000000000..858bb853c --- /dev/null +++ b/ui/coreShared/build/tests.mts @@ -0,0 +1,88 @@ +import * as path from 'path' +import { promises as fs } from 'fs' +import * as process from 'node:process' +import { getUiAppPaths, getUiCoreSharedPaths, isUiAppId, parseUiAppId, type UiAppId } from './appPaths.mts' + +export type TestBuildTarget = UiAppId | 'coreShared' + +export const TEST_BUILD_TARGET_IDS = ['coreShared', 'zoltar', 'statoblast', 'trading'] as const + +export function isTestBuildTarget(candidate: string): candidate is TestBuildTarget { + return candidate === 'coreShared' || isUiAppId(candidate) +} + +export function parseTestBuildTarget(candidate: string | undefined, context: string): TestBuildTarget { + if (candidate === 'coreShared') return candidate + return parseUiAppId(candidate, context) +} + +export function getTestBuildRoots(target: TestBuildTarget) { + if (target === 'coreShared') { + const { coreSharedTestSourceRoot, coreSharedTestOutputRoot } = getUiCoreSharedPaths() + return { testSourceRoot: coreSharedTestSourceRoot, testOutputRoot: coreSharedTestOutputRoot } + } + const { appSourceRoot, appGeneratedJsRoot } = getUiAppPaths(target) + return { testSourceRoot: path.join(appSourceRoot, 'tests'), testOutputRoot: path.join(appGeneratedJsRoot, 'tests') } +} + +async function getAllFiles(dirPath: string, fileList: string[] = []) { + const entries = await fs.readdir(dirPath, { withFileTypes: true }) + for (const entry of entries) { + const entryPath = path.join(dirPath, entry.name) + if (entry.isDirectory()) { + await getAllFiles(entryPath, fileList) + } else { + fileList.push(entryPath) + } + } + return fileList +} + +export async function buildTests(target: TestBuildTarget) { + const { testSourceRoot, testOutputRoot } = getTestBuildRoots(target) + const testFiles = (await getAllFiles(testSourceRoot)).filter(filePath => filePath.endsWith('.ts') || filePath.endsWith('.tsx')) + await fs.rm(testOutputRoot, { recursive: true, force: true }) + await fs.mkdir(testOutputRoot, { recursive: true }) + for (const testFile of testFiles) { + const source = await fs.readFile(testFile, 'utf8') + const loader = path.extname(testFile) === '.tsx' ? 'tsx' : 'ts' + let transformSource = source + if (loader === 'tsx') { + const hasH = /\bimport\s*\{[^}]*\bh\b[^}]*\}\s*from\s*['"]preact['"]/.test(source) + const hasFragment = /\bimport\s*\{[^}]*\bFragment\b[^}]*\}\s*from\s*['"]preact['"]/.test(source) + if (!hasH && !hasFragment) { + transformSource = `import { h, Fragment } from 'preact'\n${source}` + } else if (!hasH) { + transformSource = `import { h } from 'preact'\n${source}` + } else if (!hasFragment) { + transformSource = `import { Fragment } from 'preact'\n${source}` + } + } + const code = await new Bun.Transpiler({ + loader, + tsconfig: { + compilerOptions: { + jsx: 'react', + jsxFactory: 'h', + jsxFragmentFactory: 'Fragment', + }, + }, + }).transform(transformSource) + const outputFile = path.join(testOutputRoot, `${path.relative(testSourceRoot, testFile).replace(/\.[^.]+$/, '')}.js`) + await fs.mkdir(path.dirname(outputFile), { recursive: true }) + await fs.writeFile(outputFile, code) + } + return testFiles.length +} + +if (import.meta.main) { + const target = parseTestBuildTarget(process.argv[2] ?? process.env['UI_APP'], 'the UI test build') + buildTests(target) + .then(count => { + console.log(`Built ${count.toString()} ${target} UI test file(s)`) + }) + .catch(error => { + console.error(error) + process.exit(1) + }) +} diff --git a/ui/coreShared/build/testsBuild.test.ts b/ui/coreShared/build/testsBuild.test.ts new file mode 100644 index 000000000..edb5cdcba --- /dev/null +++ b/ui/coreShared/build/testsBuild.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, test } from 'bun:test' +import * as path from 'node:path' +import { existsSync, promises as fs } from 'node:fs' +import { buildTests, getTestBuildRoots, isTestBuildTarget, parseTestBuildTarget } from './tests.mts' +import { UI_APP_IDS, getUiAppPaths, getUiCoreSharedPaths } from './appPaths.mts' + +describe('UI test build target parsing', () => { + test('accepts every supported app ID and coreShared', () => { + expect(parseTestBuildTarget('coreShared', 'the test')).toBe('coreShared') + for (const appId of UI_APP_IDS) expect(parseTestBuildTarget(appId, 'the test')).toBe(appId) + }) + + test('rejects missing and unknown IDs with a clear error', () => { + expect(() => parseTestBuildTarget(undefined, 'the UI test build')).toThrow(/Missing UI app ID.*zoltar, statoblast, trading/) + expect(() => parseTestBuildTarget('', 'the UI test build')).toThrow(/Missing UI app ID/) + expect(() => parseTestBuildTarget('core-shared', 'the UI test build')).toThrow(/Unknown UI app ID 'core-shared'/) + expect(() => parseTestBuildTarget('ui', 'the UI test build')).toThrow(/Unknown UI app ID 'ui'/) + }) + + test('isTestBuildTarget matches only the supported targets', () => { + expect(isTestBuildTarget('coreShared')).toBe(true) + expect(isTestBuildTarget('zoltar')).toBe(true) + expect(isTestBuildTarget('statoblast')).toBe(true) + expect(isTestBuildTarget('trading')).toBe(true) + expect(isTestBuildTarget('monolith')).toBe(false) + }) +}) + +describe('UI test build output paths', () => { + test('each target compiles into its own package output tree', () => { + const coreShared = getUiCoreSharedPaths() + expect(getTestBuildRoots('coreShared')).toEqual({ testSourceRoot: coreShared.coreSharedTestSourceRoot, testOutputRoot: coreShared.coreSharedTestOutputRoot }) + for (const appId of UI_APP_IDS) { + const appPaths = getUiAppPaths(appId) + expect(getTestBuildRoots(appId)).toEqual({ testSourceRoot: path.join(appPaths.appSourceRoot, 'tests'), testOutputRoot: path.join(appPaths.appGeneratedJsRoot, 'tests') }) + } + }) + + test('building a target emits compiled output into only that target tree', async () => { + for (const appId of UI_APP_IDS) { + const { testSourceRoot, testOutputRoot } = getTestBuildRoots(appId) + if (!existsSync(testSourceRoot)) continue + const builtCount = await buildTests(appId) + expect(builtCount).toBeGreaterThan(0) + expect(existsSync(testOutputRoot)).toBe(true) + const compiledFiles = (await fs.readdir(testOutputRoot, { recursive: true })).filter(entry => String(entry).endsWith('.js')) + expect(compiledFiles.length).toBe(builtCount) + } + const { coreSharedTestSourceRoot, coreSharedTestOutputRoot } = getUiCoreSharedPaths() + if (existsSync(coreSharedTestSourceRoot)) { + const builtCount = await buildTests('coreShared') + expect(builtCount).toBeGreaterThan(0) + expect(existsSync(coreSharedTestOutputRoot)).toBe(true) + } + }, 60000) +}) diff --git a/ui/build/vendor.mts b/ui/coreShared/build/vendor.mts similarity index 89% rename from ui/build/vendor.mts rename to ui/coreShared/build/vendor.mts index fa682bfec..85f5a2eb8 100644 --- a/ui/build/vendor.mts +++ b/ui/coreShared/build/vendor.mts @@ -1,6 +1,7 @@ import * as path from 'path' -import * as url from 'url' import { promises as fs } from 'fs' +import { fileURLToPath } from 'node:url' +import { getUiAppPaths, parseUiAppIdFromProcess } from './appPaths.mts' import { normalizeBundlerPath, resolveBundlerSpecifierPath } from './bundlerPaths.mts' type FileType = 'file' | 'directory' type VendorBuildSteps = { @@ -30,15 +31,16 @@ async function recursiveDirectoryCopy(source: string, destination: string, inclu } import { copyProjectArtifacts } from './projectArtifacts.mts' -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const UI_ROOT_PATH = path.join(directoryOfThisFile, '..') -const VENDOR_OUTPUT_PATH = path.join(UI_ROOT_PATH, 'vendor') +function getVendorOutputPath() { + return path.join(getUiAppPaths(parseUiAppIdFromProcess('vendor build')).appRoot, 'vendor') +} type Dependency = { packageName: string; packageToVendor?: string; subfolderToVendor: string; mainEntrypointFile: string; alternateEntrypoints: Record<string, string> } const dependencyPaths: Dependency[] = [ { packageName: 'preact', subfolderToVendor: 'dist', mainEntrypointFile: 'preact.module.js', alternateEntrypoints: {} }, { packageName: 'preact/jsx-runtime', subfolderToVendor: 'dist', mainEntrypointFile: 'jsxRuntime.module.js', alternateEntrypoints: {} }, { packageName: 'preact/hooks', subfolderToVendor: 'dist', mainEntrypointFile: 'hooks.module.js', alternateEntrypoints: {} }, + { packageName: 'preact/compat', subfolderToVendor: 'dist', mainEntrypointFile: 'compat.module.js', alternateEntrypoints: {} }, { packageName: '@preact/signals', subfolderToVendor: 'dist', mainEntrypointFile: 'signals.module.js', alternateEntrypoints: {} }, { packageName: '@preact/signals-core', subfolderToVendor: 'dist', mainEntrypointFile: 'signals-core.module.js', alternateEntrypoints: {} }, { packageName: 'abitype', subfolderToVendor: 'dist/esm', mainEntrypointFile: 'exports/index.js', alternateEntrypoints: {} }, @@ -52,7 +54,7 @@ const dependencyPaths: Dependency[] = [ { packageName: 'ox', subfolderToVendor: '_esm', mainEntrypointFile: 'index.js', alternateEntrypoints: { BlockOverrides: 'core/BlockOverrides.js', AbiConstructor: 'core/AbiConstructor.js', AbiFunction: 'core/AbiFunction.js' } }, ] -async function vendorDependencies() { +async function vendorDependencies(vendorOutputPath = getVendorOutputPath()) { async function inclusionPredicate(path: string, fileType: FileType) { if (path.endsWith('.js')) return true if (path.endsWith('.ts')) return true @@ -71,7 +73,7 @@ async function vendorDependencies() { for (let segmentIndex = 1; segmentIndex < mainEntrypointSegments; segmentIndex++) { sourceDirectoryPath = path.dirname(sourceDirectoryPath) } - const destinationDirectoryPath = path.join(VENDOR_OUTPUT_PATH, packageToVendor || packageName) + const destinationDirectoryPath = path.join(vendorOutputPath, packageToVendor || packageName) await recursiveDirectoryCopy(sourceDirectoryPath, destinationDirectoryPath, inclusionPredicate, rewriteSourceMapSourcePath.bind(undefined, packageName)) } } @@ -91,8 +93,8 @@ async function rewriteSourceMapSourcePath(packageName: string, sourcePath: strin await fs.writeFile(destinationPath, JSON.stringify(fileContents)) } -async function bundleTevm() { - const tevmOutRoot = path.join(VENDOR_OUTPUT_PATH, 'tevm') +async function bundleTevm(vendorOutputPath = getVendorOutputPath()) { + const tevmOutRoot = path.join(vendorOutputPath, 'tevm') await Promise.all([ Bun.build({ entrypoints: [resolveBundlerSpecifierPath('tevm')], @@ -125,11 +127,11 @@ export async function vendor(steps: VendorBuildSteps = defaultVendorBuildSteps) await steps.copyProjectArtifacts() } -export async function clearVendorOutput(vendorOutputPath = VENDOR_OUTPUT_PATH) { +export async function clearVendorOutput(vendorOutputPath = getVendorOutputPath()) { await fs.rm(vendorOutputPath, { recursive: true, force: true }) } -const currentScriptPath = url.fileURLToPath(import.meta.url) +const currentScriptPath = fileURLToPath(import.meta.url) const invokedScriptPath = process.argv[1] if (invokedScriptPath !== undefined && path.resolve(invokedScriptPath) === currentScriptPath) { diff --git a/ui/build/watch.mts b/ui/coreShared/build/watch.mts similarity index 83% rename from ui/build/watch.mts rename to ui/coreShared/build/watch.mts index 7fe14b624..fd6130e7f 100644 --- a/ui/build/watch.mts +++ b/ui/coreShared/build/watch.mts @@ -2,28 +2,32 @@ import { spawn } from 'node:child_process' import * as fs from 'node:fs' import * as path from 'node:path' import * as process from 'node:process' -import * as url from 'node:url' - -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const UI_ROOT_PATH = path.join(directoryOfThisFile, '..') -const REPOSITORY_ROOT_PATH = path.join(UI_ROOT_PATH, '..') -const DEV_SERVER_PATH = path.join(UI_ROOT_PATH, 'dev-server.ts') -const INDEX_HTML_PATH = path.join(UI_ROOT_PATH, 'index.html') +import { getUiAppDependencyOrder, getUiAppPaths, parseUiAppIdFromProcess, type UiAppId } from './appPaths.mts' + +const appId = parseUiAppIdFromProcess('the UI watch process') +const appPaths = getUiAppPaths(appId) +const UI_ROOT_PATH = appPaths.coreSharedRoot +const APP_ROOT_PATH = appPaths.appRoot +const REPOSITORY_ROOT_PATH = appPaths.repositoryRoot +const DEV_SERVER_PATH = appPaths.devServerScript +const INDEX_HTML_PATH = appPaths.appIndexHtml const SHARED_SOURCE_ROOT_PATH = path.join(REPOSITORY_ROOT_PATH, 'shared', 'ts') const SHARED_TSCONFIG_PATH = path.join(REPOSITORY_ROOT_PATH, 'shared', 'tsconfig.json') const SOLIDITY_CONTRACTS_ROOT_PATH = path.join(REPOSITORY_ROOT_PATH, 'solidity', 'contracts') const SOLIDITY_ABI_INPUT_PATH = path.join(REPOSITORY_ROOT_PATH, 'solidity', 'ts', 'abi', 'abis.ts') const SOLIDITY_COMPILE_INPUT_PATH = path.join(REPOSITORY_ROOT_PATH, 'solidity', 'ts', 'compile.ts') const SOLIDITY_ARTIFACTS_JSON_PATH = path.join(REPOSITORY_ROOT_PATH, 'solidity', 'artifacts', 'Contracts.json') -const PROJECT_ARTIFACT_BUILD_PATH = path.join(UI_ROOT_PATH, 'build', 'projectArtifacts.mts') -const BUNDLER_PATHS_BUILD_PATH = path.join(UI_ROOT_PATH, 'build', 'bundlerPaths.mts') -const TYPE_SCRIPT_OUTPUT_PATH = path.join(UI_ROOT_PATH, 'js') -const TYPE_SCRIPT_SOURCE_PATH = path.join(UI_ROOT_PATH, 'ts') -const VENDOR_BUILD_PATH = path.join(UI_ROOT_PATH, 'build', 'vendor.mts') -const VENDOR_INPUT_PATHS = [VENDOR_BUILD_PATH, BUNDLER_PATHS_BUILD_PATH, path.join(UI_ROOT_PATH, 'package.json'), path.join(UI_ROOT_PATH, 'tsconfig.vendor.json'), path.join(UI_ROOT_PATH, 'bun.lock')] -const WORKER_BUILD_PATH = path.join(UI_ROOT_PATH, 'build', 'workers.mts') +const PROJECT_ARTIFACT_BUILD_PATH = appPaths.projectArtifactsScript +const BUNDLER_PATHS_BUILD_PATH = appPaths.bundlerPathsScript +const TYPE_SCRIPT_PROJECT_ROOT_PATHS = getUiAppDependencyOrder(appId).map(packageId => path.join(appPaths.uiRoot, packageId)) +const TYPE_SCRIPT_OUTPUT_PATHS = TYPE_SCRIPT_PROJECT_ROOT_PATHS.map(projectRoot => path.join(projectRoot, 'js')) +const TYPE_SCRIPT_SOURCE_PATHS = TYPE_SCRIPT_PROJECT_ROOT_PATHS.map(projectRoot => path.join(projectRoot, 'ts')) +const VENDOR_BUILD_PATH = appPaths.vendorBuildScript +const VENDOR_INPUT_PATHS = [VENDOR_BUILD_PATH, BUNDLER_PATHS_BUILD_PATH, path.join(APP_ROOT_PATH, 'package.json')] +const WORKER_BUILD_PATH = appPaths.workersBuildScript const WORKER_INPUT_PATHS = [WORKER_BUILD_PATH, BUNDLER_PATHS_BUILD_PATH] -const LIVE_RELOAD_ENDPOINT = 'http://127.0.0.1:12345/__live-reload' +const liveReloadEndpoints: Record<UiAppId, string> = { statoblast: 'http://127.0.0.1:12347/__live-reload', trading: 'http://127.0.0.1:4163/__live-reload', zoltar: 'http://127.0.0.1:12346/__live-reload' } +const LIVE_RELOAD_ENDPOINT = liveReloadEndpoints[appId] const BUN_EXECUTABLE_PATH = process.execPath type ManagedProcess = ReturnType<typeof spawn> @@ -34,7 +38,7 @@ let serverProcess: ManagedProcess | undefined let sharedBuildProcess: ManagedProcess | undefined let sharedBuildQueued = false let sharedBuildRunning = false -let typeScriptWatchProcess: ManagedProcess | undefined +const typeScriptWatchProcesses: ManagedProcess[] = [] let vendorBuildProcess: ManagedProcess | undefined let vendorBuildRunning = false let vendorBuildQueued = false @@ -170,7 +174,7 @@ const sendLiveReload = async (reason: string) => { const spawnServer = () => { console.log('[ui:watch] Starting ui:serve') try { - serverProcess = spawn(BUN_EXECUTABLE_PATH, [DEV_SERVER_PATH], { + serverProcess = spawn(BUN_EXECUTABLE_PATH, [DEV_SERVER_PATH, appId], { cwd: REPOSITORY_ROOT_PATH, stdio: 'inherit', }) @@ -313,23 +317,25 @@ const watchDirectoryForContractSources = (directoryPath: string, refreshWatchers const refreshTypeScriptOutputWatchers = async () => { clearTypeScriptOutputWatchers() - const directories = await getAllDirectories(TYPE_SCRIPT_OUTPUT_PATH) - for (const directoryPath of directories) { - watchDirectoryForTypeScriptOutputs(directoryPath, () => { - void refreshTypeScriptOutputWatchers() - }) - } - const files = await getAllFiles(TYPE_SCRIPT_OUTPUT_PATH) - for (const filePath of files) { - watchFileWithCleanup( - filePath, - relativePath => { - queueLiveReload(relativePath) - }, - callback => { - typeScriptOutputUnwatchCallbacks.push(callback) - }, - ) + for (const outputPath of TYPE_SCRIPT_OUTPUT_PATHS) { + const directories = await getAllDirectories(outputPath) + for (const directoryPath of directories) { + watchDirectoryForTypeScriptOutputs(directoryPath, () => { + void refreshTypeScriptOutputWatchers() + }) + } + const files = await getAllFiles(outputPath) + for (const filePath of files) { + watchFileWithCleanup( + filePath, + relativePath => { + queueLiveReload(relativePath) + }, + callback => { + typeScriptOutputUnwatchCallbacks.push(callback) + }, + ) + } } } @@ -357,23 +363,25 @@ const refreshSharedSourceWatchers = async () => { const refreshTypeScriptSourceWatchers = async () => { clearTypeScriptSourceWatchers() - const directories = await getAllDirectories(TYPE_SCRIPT_SOURCE_PATH) - for (const directoryPath of directories) { - watchDirectoryForTypeScriptSources(directoryPath, () => { - void refreshTypeScriptSourceWatchers() - }) - } - const files = await getAllFiles(TYPE_SCRIPT_SOURCE_PATH) - for (const filePath of files) { - watchFileWithCleanup( - filePath, - relativePath => { - void runWorkerBuild(relativePath) - }, - callback => { - typeScriptSourceUnwatchCallbacks.push(callback) - }, - ) + for (const sourcePath of TYPE_SCRIPT_SOURCE_PATHS) { + const directories = await getAllDirectories(sourcePath) + for (const directoryPath of directories) { + watchDirectoryForTypeScriptSources(directoryPath, () => { + void refreshTypeScriptSourceWatchers() + }) + } + const files = await getAllFiles(sourcePath) + for (const filePath of files) { + watchFileWithCleanup( + filePath, + relativePath => { + void runWorkerBuild(relativePath) + }, + callback => { + typeScriptSourceUnwatchCallbacks.push(callback) + }, + ) + } } } @@ -457,7 +465,7 @@ const runVendorBuild = async (reason: string) => { vendorBuildRunning = true console.log(`[ui:watch] Rebuilding UI vendor assets because ${reason} changed`) try { - vendorBuildProcess = spawn(BUN_EXECUTABLE_PATH, [VENDOR_BUILD_PATH], { + vendorBuildProcess = spawn(BUN_EXECUTABLE_PATH, [VENDOR_BUILD_PATH, appId], { cwd: UI_ROOT_PATH, stdio: 'inherit', }) @@ -506,7 +514,7 @@ const runWorkerBuild = async (reason: string) => { workerBuildRunning = true console.log(`[ui:watch] Rebuilding simulation worker because ${reason} changed`) try { - workerBuildProcess = spawn(BUN_EXECUTABLE_PATH, [WORKER_BUILD_PATH], { + workerBuildProcess = spawn(BUN_EXECUTABLE_PATH, [WORKER_BUILD_PATH, appId], { cwd: UI_ROOT_PATH, stdio: 'inherit', }) @@ -561,6 +569,7 @@ const runSharedBuild = async (reason: string) => { await runSharedBuild('queued shared input') return } + await runWorkerBuild(reason) queueLiveReload(reason) } @@ -686,35 +695,38 @@ const shutdown = async (exitCode: number) => { await stopProcess(contractBuildProcess) await stopProcess(projectArtifactBuildProcess) await stopProcess(serverProcess) - await stopProcess(typeScriptWatchProcess) + for (const typeScriptWatchProcess of typeScriptWatchProcesses) await stopProcess(typeScriptWatchProcess) process.exit(exitCode) } const main = () => { console.log('[ui:watch] Watching UI TypeScript output and serving static assets') - try { - typeScriptWatchProcess = spawn(BUN_EXECUTABLE_PATH, ['x', 'tsc', '--project', 'tsconfig.json', '--watch', '--preserveWatchOutput'], { - cwd: UI_ROOT_PATH, - stdio: ['inherit', 'pipe', 'pipe'], + for (const projectRoot of TYPE_SCRIPT_PROJECT_ROOT_PATHS) { + let typeScriptWatchProcess: ManagedProcess + try { + typeScriptWatchProcess = spawn(BUN_EXECUTABLE_PATH, ['x', 'tsc', '--project', 'tsconfig.json', '--watch', '--preserveWatchOutput'], { + cwd: projectRoot, + stdio: ['inherit', 'pipe', 'pipe'], + }) + typeScriptWatchProcesses.push(typeScriptWatchProcess) + } catch (error) { + console.error(`[ui:watch] Failed to start TypeScript watch for ${path.basename(projectRoot)}`) + console.error(error) + void shutdown(1) + return + } + const label = `${path.basename(projectRoot)} TypeScript watch` + attachProcessErrorHandler(typeScriptWatchProcess, label) + if (typeScriptWatchProcess.stdout === null || typeScriptWatchProcess.stderr === null) throw new Error(`${label} streams are unavailable`) + typeScriptWatchProcess.stdout.on('data', onTypeScriptWatchStdout) + typeScriptWatchProcess.stderr.on('data', onTypeScriptWatchStderr) + typeScriptWatchProcess.on('exit', (exitCode, signalCode) => { + if (shuttingDown) return + const failureCode = exitCode ?? 1 + console.error(`[ui:watch] ${label} exited unexpectedly (${signalCode ?? failureCode})`) + void shutdown(failureCode) }) - } catch (error) { - console.error('[ui:watch] Failed to start TypeScript watch') - console.error(error) - void shutdown(1) - return - } - attachProcessErrorHandler(typeScriptWatchProcess, 'TypeScript watch') - if (typeScriptWatchProcess.stdout === null || typeScriptWatchProcess.stderr === null) { - throw new Error('TypeScript watch streams are unavailable') } - typeScriptWatchProcess.stdout.on('data', onTypeScriptWatchStdout) - typeScriptWatchProcess.stderr.on('data', onTypeScriptWatchStderr) - typeScriptWatchProcess.on('exit', (exitCode, signalCode) => { - if (shuttingDown) return - const failureCode = exitCode ?? 1 - console.error(`[ui:watch] TypeScript watch exited unexpectedly (${signalCode ?? failureCode})`) - void shutdown(failureCode) - }) spawnServer() @@ -778,11 +790,14 @@ const main = () => { void (async () => { try { - const cssFiles = await getAllFiles(path.join(UI_ROOT_PATH, 'css')) - for (const cssFile of cssFiles) { - watchFile(cssFile, relativePath => { - queueLiveReload(relativePath) - }) + const cssRootPaths = [path.join(UI_ROOT_PATH, 'css'), path.join(APP_ROOT_PATH, 'css')].filter(cssRootPath => fs.existsSync(cssRootPath)) + for (const cssRootPath of new Set(cssRootPaths)) { + const cssFiles = await getAllFiles(cssRootPath) + for (const cssFile of cssFiles) { + watchFile(cssFile, relativePath => { + queueLiveReload(relativePath) + }) + } } } catch (error) { console.error('[ui:watch] Failed to load CSS files for watching') @@ -798,4 +813,4 @@ const main = () => { }) } -main() +if (import.meta.main) main() diff --git a/ui/build/workers.mts b/ui/coreShared/build/workers.mts similarity index 83% rename from ui/build/workers.mts rename to ui/coreShared/build/workers.mts index e8172dcb1..d307498a5 100644 --- a/ui/build/workers.mts +++ b/ui/coreShared/build/workers.mts @@ -1,10 +1,9 @@ import * as path from 'path' -import * as url from 'url' import { promises as fs } from 'fs' +import { getUiAppPaths, parseUiAppIdFromProcess } from './appPaths.mts' import { normalizeBundlerPath } from './bundlerPaths.mts' -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const UI_ROOT_PATH = path.join(directoryOfThisFile, '..') +const appPaths = getUiAppPaths(parseUiAppIdFromProcess('worker build')) const WORKER_BANNER = ` const process = globalThis.process ?? { @@ -21,13 +20,12 @@ globalThis.process ??= process globalThis.global ??= globalThis `.trim() -const workerEntryPath = path.join(UI_ROOT_PATH, 'ts', 'simulation', 'tevmWorker.ts') const BANNER_LINE_COUNT = WORKER_BANNER.split('\n').length const result = await Bun.build({ - entrypoints: [normalizeBundlerPath(workerEntryPath)], + entrypoints: [normalizeBundlerPath(appPaths.workerEntrypoint)], naming: { entry: 'tevmWorker.worker.js' }, - outdir: path.join(UI_ROOT_PATH, 'js', 'simulation'), + outdir: path.join(appPaths.appGeneratedJsRoot, 'simulation'), target: 'browser', sourcemap: 'linked', }) diff --git a/trading/bun.lock b/ui/coreShared/bun.lock similarity index 80% rename from trading/bun.lock rename to ui/coreShared/bun.lock index 0edc84312..c162b1e30 100644 --- a/trading/bun.lock +++ b/ui/coreShared/bun.lock @@ -3,23 +3,37 @@ "configVersion": 1, "workspaces": { "": { - "name": "@zoltar/trading", + "name": "@zoltar/ui-core-shared", "dependencies": { + "@noble/curves": "2.2.0", + "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", - "@zoltar/shared": "file:../shared", + "@zoltar/shared": "file:../../shared", + "buffer": "6.0.3", + "micro-eth-signer": "0.18.1", + "micro-packed": "0.8.0", "preact": "10.26.4", + "source-map": "0.6.1", "tevm": "1.0.0-rc.151", }, "devDependencies": { - "@biomejs/biome": "2.3.11", + "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", - "solc": "0.8.35", + "funtypes": "5.1.1", "typescript": "5.8.2", }, }, }, "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", + "brace-expansion": "5.0.9", + "fast-uri": "3.1.5", + "nanoid": "3.3.18", + "postcss": "8.5.23", "tmp": "0.2.7", + "ws": "8.21.0", }, "packages": { "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], @@ -28,77 +42,59 @@ "@ark/util": ["@ark/util@0.2.2", "", {}, "sha512-ryZ4+f3SlReQRH9nTFLK5EeU1Pan5ZfS+ACPSk0ir5uujJouFmvOdnkVfeAJAgeOb3kKmUM9kjelv1cwH2ScZg=="], - "@biomejs/biome": ["@biomejs/biome@2.3.11", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.11", "@biomejs/cli-darwin-x64": "2.3.11", "@biomejs/cli-linux-arm64": "2.3.11", "@biomejs/cli-linux-arm64-musl": "2.3.11", "@biomejs/cli-linux-x64": "2.3.11", "@biomejs/cli-linux-x64-musl": "2.3.11", "@biomejs/cli-win32-arm64": "2.3.11", "@biomejs/cli-win32-x64": "2.3.11" }, "bin": { "biome": "bin/biome" } }, "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.11", "", { "os": "win32", "cpu": "x64" }, "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg=="], - "@effect/schema": ["@effect/schema@0.75.5", "", { "dependencies": { "fast-check": "^3.21.0" }, "peerDependencies": { "effect": "^3.9.2" } }, "sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], "@ethereumjs/binarytree": ["@ethereumjs/binarytree@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-szugxzYrQHRvlhbu0BrcW/Kw7tg3K0b1HbKCdwRrFhvz80RbGfML2wqDDZhO5NyvHqWpS+Rt9q+qV9LxOHCP6w=="], @@ -164,7 +160,7 @@ "@openzeppelin/contracts": ["@openzeppelin/contracts@5.6.1", "", {}, "sha512-Ly6SlsVJ3mj+b18W3R8gNufB7dTICT105fJhodGAGgyC2oqnBAhqSiNDJ8V8DLY05cCz81GLI0CU5vNYA1EC/w=="], - "@oxc-project/types": ["@oxc-project/types@0.142.0", "", {}, "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ=="], + "@oxc-project/types": ["@oxc-project/types@0.144.0", "", {}, "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg=="], "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], @@ -174,33 +170,33 @@ "@preact/signals-core": ["@preact/signals-core@1.14.4", "", {}, "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-l7x215OGvo1s52JWmR8U/DAVzEDWBCIbTm28aeJV/WDTSHgcKXaZTuBT0hJMs5NggilfJTW3clZVvd24yfKJxA=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9u9Xv6c1AJZT0FfwH5vrMG5Jjcwhc1MlyrPu0XfTqkzsmqfks2M6W/o5XwAJgVVN/jHpqqngC1WevHKKTIUtIA=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-9W1mbGZAfW3oqd85bhBkmpyHCCzL1TeG/zFFP3vg7b0rlly8cxOcre5nXwz+LHazCwac2MNWgPdPCHndABjpWQ=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0p1lhiCSCyaerFwtrdZQUx7NqGk6LQnaRKWX7tFQqwQgvX0rjM15cIkm3pax1UpEakK14C4mOxx/jSqCBdBRqQ=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-e+cOJXrJ2L3zx6YzqPg+f6Wbk3V1cKB8bOhbaYdVYN3DdquzNdRAmrbETz1qnt5yp/c7JNlNjmITiA2cVneQ7w=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-JsSMsj6sNat/MuhG5fnBD7QgbtpHKVe30x5/bAVirDHdhoQRXJkF6xc0Jqk8O4fiCUQAzMOoH9wZi3m60c8wtg=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-B5G/zJdHaoJn9vD50eGHWkiWfmq8Uhi3IiLPJTzmZTrAalk1bztUikSXo0qga18ibE0IXboyeMUnhPjhAJ45wQ=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6mC/awzKka8W6EoekjegpfGkjz8jXWDX63pqu/HYVpyKtZfu65Jsh4QAH3Kej3CAv/c1oGX7psTmFEbr0mDxLA=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-412MX9fJLdA1IK28EZnc8jYv2HRTleOZgfLQumJ5zy7OeJLZlg/CETwFaXjNmGVxG51cFHpKLqb5LKvBC+HsHA=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Q/+HI/ToJafZ1iCqGgVQXUEkIjufHCTF0gBQ2a5o3cg7GJ2h0qyq3nvvSmU+bGda2/7ygXpTY4TM6gO9OhQ0ZA=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ZKp/w41n6wCvxzxQHtQSbuphfX3Y4cCvbjkKHusrLx4lh+JWLTU7StSltO/DKARISzbj368d+qUaCjI8K2wzXw=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.2", "", { "os": "none", "cpu": "arm64" }, "sha512-pxE6xD4KS3eAROkKK5yrhB9/3+vhlhVGMvlQLbdpzrBGDbKrnzx3RLwPaHvasLo6jgaiBLn7e04Df9C4tYhjmA=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.4", "", { "os": "none", "cpu": "arm64" }, "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-4MqEue5re+xIZzAWsB8sj0P1kqZySWqIuN4t6QaIO/YA6SFwySOLruvWQFzfmqk8LBK2P30KCSJwf6mJCZZ5/A=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-NweNxxD0Nf9t8v7kodun45Ijp3EIwYY+uydPP6qBEYvfBqhIjN6dZMzlQja3tqX/aLs3F3Uz+AxDpKgRhpOZQg=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], @@ -218,7 +214,7 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@tevm/actions": ["@tevm/actions@1.0.0-rc.153", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.153", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3", "zod": "^4.1.11" } }, "sha512-ZpxxaVfoxfEhP+A4Wd3Md18fdxAygTpe8OhaNgBEFxS8FvolCGFdGCwxh4utjPBH5BT770pKLRTqJwYqu8wN5w=="], + "@tevm/actions": ["@tevm/actions@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3", "zod": "^4.1.11" } }, "sha512-A1ICMc83VK/cauNu/47QPg5pj5vcqvNjl+5JdXJMJKFTkisYqTOkwujoh2tabiylCdkOQL0LATP+BmUt4xPUuA=="], "@tevm/address": ["@tevm/address@1.0.0-rc.151", "", { "dependencies": { "@tevm/errors": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-w86dds+px4s/KHfbgxYheyuEk+ZPCayzLWIUzEU/vkvpR7hXLwbolFmogncBPv4zfFOB95ZnopVWxyP2TEbFug=="], @@ -262,7 +258,7 @@ "@tevm/memory-client": ["@tevm/memory-client@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/address": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "kzg-wasm": "^1.0.0", "vitest": "^4.1.6" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-l7SG85t5OLZv17Gu73pVUnG+Y7D+WCp5tkYYiMn0INtbHG5q3MU1iaeubXcNvSSyZAxxwHtX/I7J9X5I7GL/tA=="], - "@tevm/node": ["@tevm/node@1.0.0-rc.153", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/evm": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-vdFIDRqCbRHO4v6YPwaawS1ltDS02WihpN+l8MIEg1fBbcJTPeZ0vLDqp3gAuqKoRF0KyediUSG55i8x0ZKI5Q=="], + "@tevm/node": ["@tevm/node@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/evm": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-lJLaX1QARvwzWhG6ly/l1ZP6jK5XnBmARUM9dUYCP+9S/owixjd7CrI4k+AVCVBEi9yajT45o4j2qa0Opcl+BQ=="], "@tevm/precompiles": ["@tevm/precompiles@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@noble/curves": "^2.2.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-mABFuxsZ2OhQPKIMAocwkT42cD1G9OI3vBGuQ0dbpheHdbm7hmClmazySd1fV22exBcC+IIhgJYdtOQT3mozCw=="], @@ -278,7 +274,7 @@ "@tevm/runtime": ["@tevm/runtime@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "effect": "3.21.2" } }, "sha512-eZi4g01PPmTIeS6uBfJQX31YxLEXE3LoRV2z63XcKBrEpe8WwAp/M4Ob90+9+qxtd4hS8mv0CkX1JE5eLhTroQ=="], - "@tevm/server": ["@tevm/server@1.0.0-rc.153", "", { "dependencies": { "@tevm/common": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "commander": "^14.0.3", "ws": "^8.20.1", "zod": "^4.4.3" } }, "sha512-hszN3nDKYmtGgEnDTzL/JMLFanjFaE13QKOnnIbls3o7JraFpUmrzfuQRSwhp8UYOwCf0Pe7AiNDzMdfGeQfew=="], + "@tevm/server": ["@tevm/server@1.0.0-rc.151", "", { "dependencies": { "@tevm/common": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "commander": "^14.0.3", "zod": "^4.4.3" } }, "sha512-3BBNPReFgKUnycYzQs0YdgnQ+NXXrTyqSUjoPel6MEic4Iaq/VOqpzq16ykgK+zhryEkVloFAz7oSd7QXOlypw=="], "@tevm/solc": ["@tevm/solc@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "abitype": "^1.2.4", "solidity-ast": "^0.4.61" } }, "sha512-LYNDMNz9JyS+NBMQJJqeSbzaazrDnJ5r2iFtTISFmdGHCDFoXJVNgCMx5ZmpvhrE0XOPueZQGUri1nV25Jsecg=="], @@ -318,25 +314,59 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], + "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], "@types/readable-stream": ["@types/readable-stream@2.3.15", "", { "dependencies": { "@types/node": "*", "safe-buffer": "~5.1.1" } }, "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ=="], "@types/retry": ["@types/retry@0.12.1", "", {}, "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g=="], - "@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="], + "@typescript/lib-decorators": ["@better-typescript-lib/decorators@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-ZL90jrzAhnvDr/fbpJt3CgGZdWvAeoAWznp56/mHJRKrCwsaJ9uCPqU09D21FJkWsmZ2jkz+mwBI9+e5dzwFvg=="], + + "@typescript/lib-dom": ["@better-typescript-lib/dom@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-tt0ZgcgxqwRL3oWa2xgtBnq7zJjBXqrtwQo6hvHEwsKNA5SD65FVrC+b1zTFTGvTLURGDIlq1kn3cB2quurVAA=="], + + "@typescript/lib-es2015": ["@better-typescript-lib/es2015@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Xtg8WhxWAvBawWBOsAF3XurOHa18A7nvqYdfGYz4EXfrDvw32nod6zjf0it7/sDwcGIcms6wyRZyLkI4RP7e8Q=="], + + "@typescript/lib-es2016": ["@better-typescript-lib/es2016@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-RFLgskFU2rt/5esIcWibJu8n9a4EjmP6hCh5kqULb+w5J0eE9gXnhU6IZ685+spY2nPw991IYwnUP5idtfiGjQ=="], + + "@typescript/lib-es2017": ["@better-typescript-lib/es2017@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-bgCJrI6h3d6jvZsnzsbdH7/HVAV2GYrbtiUnBxV/ZTJ+VDiHdSI8CvsjIAVYHmrzLDC7FBTU+B+O8YhQ+fsehA=="], + + "@typescript/lib-es2018": ["@better-typescript-lib/es2018@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-8rwLcK9SqzO6bCsa6fQkCrYtFMAs8b5Oz+Rhuh/RmKD/Ja6XvkuXvsA8k+Zf6LjzEc/1aHk3ooQaoPeyh5wVIw=="], + + "@typescript/lib-es2019": ["@better-typescript-lib/es2019@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-S16xjOz1GJ1N9c7cdk+jywkkKjagQI8ojP9wBABU4876rv6Ng64iYVBG6IOpPcQ3eSIbnWSZVk4h4kCyJt1sKA=="], + + "@typescript/lib-es2020": ["@better-typescript-lib/es2020@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-TZMzKXvPXrxT25nhuA43uUbpBagwi5jjxfBcMPZQwbDfFAemO0oa96tQCw8L5oKxN/uxFLhRZuEwLRLVrtcjFw=="], + + "@typescript/lib-es2021": ["@better-typescript-lib/es2021@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-TCc6rj/uj3SHO9SPmsUqT3uRuZgFF2+ZLsKmA6+vQZDDVBQnelQHuooUhUWz49dh9TzntEI46x+yXen+UHfBGw=="], + + "@typescript/lib-es2022": ["@better-typescript-lib/es2022@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Gu5MihDWyEgEd7gXXmMmh2lwyZDPlJIEU8v7Mfeqlk7BfYmS4Raww7f1WYGlXT01zAUELE9vOQdOb+jWG+9N1A=="], + + "@typescript/lib-es2023": ["@better-typescript-lib/es2023@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Ezo4X5hM5bZx9LTPwexvpn1dq+JO+Od6P661pVjMoFA1eUm13SxGox2YIeISFAnWmJyLrcA9ZDr9+1cYlbeZzA=="], + + "@typescript/lib-es2024": ["@better-typescript-lib/es2024@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-1Gix0YIF/NkCid7W+/bOMivG3vIUYfOfpUNx94UDRmkTHTQa3/Vu/X2Mjes8Cu60fBhcfscMBC6QY0emStK79w=="], + + "@typescript/lib-es5": ["@better-typescript-lib/es5@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Efdc4z0hL+HQvcKmsuKtypYcwREUjm1U17eE9J3oXQ2icjR6bAD5IEgFX2gSV2/Ad1KGeBS9gakLxYvDP1G0eQ=="], - "@vitest/mocker": ["@vitest/mocker@4.1.10", "", { "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow=="], + "@typescript/lib-es6": ["@better-typescript-lib/es6@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-zjl9jCTXwWx0QRSv5ILADM1wEgLq40VzBFURVHXg0D8LYv2KYVA7ICdcJZ2/DxoLhaukJm42mllRPjr/Bylw/A=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.10", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q=="], + "@typescript/lib-esnext": ["@better-typescript-lib/esnext@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-NTWWr05QE2/oes0xEiy84on87gzzEWyk0vwPTVgcuWvgtBwDe9sIeGJQSO4vfc7ZP+s3FPBqeSPiAlB7rZi68w=="], - "@vitest/runner": ["@vitest/runner@4.1.10", "", { "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" } }, "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg=="], + "@typescript/lib-scripthost": ["@better-typescript-lib/scripthost@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-XwpujOfKtEn0oXodg8Rzm7ULu4OscK0l88ozWp4/q114k5vVd/1d90Q8nEIlB2RR8eqcGNkBG+Tcaj0qiaaJ1A=="], - "@vitest/snapshot": ["@vitest/snapshot@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw=="], + "@typescript/lib-webworker": ["@better-typescript-lib/webworker@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-9jdYsSj7YWGixeVok7xZSV+OHpOxMBEcuw+zPHj2462LS812Xq2Vs5Kk00PoFjVXolgQm6naXDG1Wa+x445BPQ=="], - "@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="], + "@vitest/expect": ["@vitest/expect@4.1.11", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw=="], - "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="], + "@vitest/mocker": ["@vitest/mocker@4.1.11", "", { "dependencies": { "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="], + + "@vitest/runner": ["@vitest/runner@4.1.11", "", { "dependencies": { "@vitest/utils": "4.1.11", "pathe": "^2.0.3" } }, "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog=="], + + "@vitest/spy": ["@vitest/spy@4.1.11", "", {}, "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA=="], + + "@vitest/utils": ["@vitest/utils@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ=="], "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], @@ -372,7 +402,7 @@ "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], - "@zoltar/shared": ["@zoltar/shared@file:../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + "@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], "abitype": ["abitype@1.3.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-fk6Te+bojIFrMvMZrnOO+SxCB+RUksTGOzq/60ZRvs1L+BVzvi2bqt9L3W/17ZLdZsyM1FuYf65P5nlmoiH1Bg=="], @@ -402,13 +432,19 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA=="], + + "better-typescript-lib": ["better-typescript-lib@2.11.0", "", { "dependencies": { "@typescript/lib-decorators": "npm:@better-typescript-lib/decorators@2.11.0", "@typescript/lib-dom": "npm:@better-typescript-lib/dom@2.11.0", "@typescript/lib-es2015": "npm:@better-typescript-lib/es2015@2.11.0", "@typescript/lib-es2016": "npm:@better-typescript-lib/es2016@2.11.0", "@typescript/lib-es2017": "npm:@better-typescript-lib/es2017@2.11.0", "@typescript/lib-es2018": "npm:@better-typescript-lib/es2018@2.11.0", "@typescript/lib-es2019": "npm:@better-typescript-lib/es2019@2.11.0", "@typescript/lib-es2020": "npm:@better-typescript-lib/es2020@2.11.0", "@typescript/lib-es2021": "npm:@better-typescript-lib/es2021@2.11.0", "@typescript/lib-es2022": "npm:@better-typescript-lib/es2022@2.11.0", "@typescript/lib-es2023": "npm:@better-typescript-lib/es2023@2.11.0", "@typescript/lib-es2024": "npm:@better-typescript-lib/es2024@2.11.0", "@typescript/lib-es5": "npm:@better-typescript-lib/es5@2.11.0", "@typescript/lib-es6": "npm:@better-typescript-lib/es6@2.11.0", "@typescript/lib-esnext": "npm:@better-typescript-lib/esnext@2.11.0", "@typescript/lib-scripthost": "npm:@better-typescript-lib/scripthost@2.11.0", "@typescript/lib-webworker": "npm:@better-typescript-lib/webworker@2.11.0" }, "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-dwaPodcG5lVepyeoEmwRSpRqKWOso5CRktnMcPKlE4jjcHLGjkBhZXIMe6EFD9ALG/Ru3kwOO1t6TNNQ6Pf1Jw=="], "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="], + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -420,7 +456,7 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], @@ -430,7 +466,7 @@ "command-exists": ["command-exists@1.2.9", "", {}, "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="], - "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], @@ -452,7 +488,7 @@ "effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="], - "electron-to-chromium": ["electron-to-chromium@1.5.400", "", {}, "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.411", "", {}, "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg=="], "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], @@ -460,13 +496,13 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="], + "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -518,6 +554,8 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "funtypes": ["funtypes@5.1.1", "", {}, "sha512-JdOe6zAk3kzTTuoTKrdshe16mr8FwuNzUnSg5gZpeJxMYFiY0LaIec9A4/VHK8Z3fSnm09eMrH93vaHE2R0J5Q=="], + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -552,6 +590,8 @@ "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], @@ -654,11 +694,11 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@3.3.17", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g=="], + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "node-releases": ["node-releases@2.0.52", "", {}, "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A=="], + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], @@ -666,7 +706,7 @@ "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], - "ox": ["ox@0.14.33", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-rooA/4o7bBof4Ge2VH/eovfNPb/AEEYyrNj03wggc55g5HZD8Pjs/OeWhttgjic3dDcqn0r29bDuvQEdTiUemQ=="], + "ox": ["ox@0.14.34", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-12seOIk7dv8eAoGQhcWaeKZxNz304IVcDvb9U5Y7JZAEVe21Nm1YMxLjhWah+su5BD4Omx4Zz0z5x3ij9M4GYQ=="], "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], @@ -704,7 +744,7 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - "postcss": ["postcss@8.5.25", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw=="], + "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], "preact": ["preact@10.26.4", "", {}, "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w=="], @@ -734,7 +774,7 @@ "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], - "rolldown": ["rolldown@1.2.2", "", { "dependencies": { "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.2", "@rolldown/binding-darwin-arm64": "1.2.2", "@rolldown/binding-darwin-x64": "1.2.2", "@rolldown/binding-freebsd-x64": "1.2.2", "@rolldown/binding-linux-arm-gnueabihf": "1.2.2", "@rolldown/binding-linux-arm64-gnu": "1.2.2", "@rolldown/binding-linux-arm64-musl": "1.2.2", "@rolldown/binding-linux-ppc64-gnu": "1.2.2", "@rolldown/binding-linux-s390x-gnu": "1.2.2", "@rolldown/binding-linux-x64-gnu": "1.2.2", "@rolldown/binding-linux-x64-musl": "1.2.2", "@rolldown/binding-openharmony-arm64": "1.2.2", "@rolldown/binding-win32-arm64-msvc": "1.2.2", "@rolldown/binding-win32-x64-msvc": "1.2.2" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-opwpo1tQBAcpSUJDt94B7hhLNGOKjCdE//XXjeLrnx9b83bjnw45tXdg1b09yEw/VLFBJGZpwRULMmOZo7ol+A=="], + "rolldown": ["rolldown@1.2.4", "", { "dependencies": { "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.4", "@rolldown/binding-darwin-arm64": "1.2.4", "@rolldown/binding-darwin-x64": "1.2.4", "@rolldown/binding-freebsd-x64": "1.2.4", "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", "@rolldown/binding-linux-arm64-gnu": "1.2.4", "@rolldown/binding-linux-arm64-musl": "1.2.4", "@rolldown/binding-linux-ppc64-gnu": "1.2.4", "@rolldown/binding-linux-s390x-gnu": "1.2.4", "@rolldown/binding-linux-x64-gnu": "1.2.4", "@rolldown/binding-linux-x64-musl": "1.2.4", "@rolldown/binding-openharmony-arm64": "1.2.4", "@rolldown/binding-win32-arm64-msvc": "1.2.4", "@rolldown/binding-win32-x64-msvc": "1.2.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w=="], "rxjs": ["rxjs@7.5.5", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw=="], @@ -758,7 +798,7 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + "solc": ["solc@0.8.36", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-cnDhjWo8dC5FxnEwCrWxUsJS7RPsHf+pVUe9Tr/+b15sQ40WCVn4vX4R+tOvN5gRfEgiquvFzfF4VV0kSnknhw=="], "solc-typed-ast": ["solc-typed-ast@18.2.6", "", { "dependencies": { "axios": "^1.6.8", "commander": "^12.0.0", "decimal.js": "^10.4.3", "findup-sync": "^5.0.0", "fs-extra": "^11.2.0", "jsel": "^1.1.6", "semver": "^7.6.0", "solc": "0.8.25", "src-location": "^1.1.0", "web3-eth-abi": "^4.2.0" }, "bin": { "sol-ast-compile": "dist/bin/compile.js" } }, "sha512-JObIRpPYO4zvQkAgr3koBr5Du+IkYzVNofykZpGv/44iJ0VRsaEq/X+LrrFD0SDqFcnyYcBOPWDiOXwNOStKlg=="], @@ -792,7 +832,7 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "terser": ["terser@5.49.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-7A2xlQ5EnGT8KPA92dUh6RbRYTVw8hEaEN9L1K68l4UOXFuV511NnAqObGoRqGOQofQcMypisu1s3xawCEHrvA=="], + "terser": ["terser@5.50.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w=="], "tevm": ["tevm@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/address": "^1.0.0-rc.151", "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/bun-plugin": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/client-types": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/compiler": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/esbuild-plugin": "^1.0.0-rc.151", "@tevm/evm": "^1.0.0-rc.151", "@tevm/http-client": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/rollup-plugin": "^1.0.0-rc.151", "@tevm/rspack-plugin": "^1.0.0-rc.151", "@tevm/server": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/ts-plugin": "^1.0.0-rc.151", "@tevm/tx": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/viem": "^1.0.0-rc.151", "@tevm/vite-plugin": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151", "@tevm/webpack-plugin": "^1.0.0-rc.151", "commander": "^14.0.3", "effect": "3.21.2", "glob": "^13.0.6", "solc": "^0.8.35" }, "peerDependencies": { "viem": "^2.49.3" }, "bin": { "tevm": "cli.js" } }, "sha512-LyFVsmOZUo4ZnFlZa4zgchK1Z2bHiXyihTlh3Ozin6IbgZa2ReJgdDz2I+UI+27IXzrRKGpMLy1WNAWbhaVSJA=="], @@ -812,7 +852,7 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tsx": ["tsx@4.23.5", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg=="], + "tsx": ["tsx@4.23.12", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q=="], "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], @@ -824,17 +864,17 @@ "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "viem": ["viem@2.55.10", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.33", "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-Q9Ba+/ma81U2M5o5P2AQ7Ux8rTIwmCZvUcr8rKdQ22bV0IBFHllM2m5gWDP8hFaUN2nH2oW3QG44amRazflYNQ=="], + "viem": ["viem@2.55.19", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.34", "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-4QPIX0eYPLsOBk53NKswVMkQoxuP7GlOBnB4wM6dkDokREO4QENNc3bmyPKK1PBTViXh0TPJCHLjIuU20Qi3fg=="], - "vite": ["vite@8.2.0", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.23", "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ=="], + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], - "vitest": ["vitest@4.1.10", "", { "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", "@vitest/pretty-format": "4.1.10", "@vitest/runner": "4.1.10", "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.10", "@vitest/browser-preview": "4.1.10", "@vitest/browser-webdriverio": "4.1.10", "@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw=="], + "vitest": ["vitest@4.1.11", "", { "dependencies": { "@vitest/expect": "4.1.11", "@vitest/mocker": "4.1.11", "@vitest/pretty-format": "4.1.11", "@vitest/runner": "4.1.11", "@vitest/snapshot": "4.1.11", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.11", "@vitest/browser-preview": "4.1.11", "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-istanbul": "4.1.11", "@vitest/coverage-v8": "4.1.11", "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw=="], "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], @@ -860,7 +900,7 @@ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - "ws": ["ws@8.21.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw=="], + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], @@ -900,7 +940,7 @@ "@latticexyz/world/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], - "@scure/bip32/@noble/curves": ["@noble/curves@1.9.0", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg=="], + "@scure/bip32/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], "@scure/bip32/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], @@ -912,6 +952,10 @@ "@tevm/base-bundler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + "@tevm/base-bundler/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + + "@tevm/bun-plugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + "@tevm/bundler-cache/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], "@tevm/compiler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], @@ -920,10 +964,12 @@ "@tevm/resolutions/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "@tevm/server/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + "@tevm/ts-plugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], "@tevm/tsupconfig/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + "@tevm/unplugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], @@ -948,6 +994,8 @@ "prettier-plugin-solidity/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "solc-typed-ast/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], "solc-typed-ast/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], @@ -958,8 +1006,6 @@ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], - "tevm/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], - "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], "viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], @@ -968,8 +1014,6 @@ "viem/abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], - "viem/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], - "web3-eth-abi/abitype": ["abitype@0.7.1", "", { "peerDependencies": { "typescript": ">=4.9.4", "zod": "^3 >=3.19.1" }, "optionalPeers": ["zod"] }, "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ=="], "web3-utils/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], @@ -988,6 +1032,10 @@ "@tevm/base-bundler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "@tevm/base-bundler/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "@tevm/bun-plugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "@tevm/bundler-cache/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], "@tevm/compiler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], @@ -996,8 +1044,12 @@ "@tevm/resolutions/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "@tevm/ts-plugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "@tevm/tsupconfig/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "@tevm/unplugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "solc-typed-ast/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "solc-typed-ast/solc/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], diff --git a/ui/css/index.css b/ui/coreShared/css/index.css similarity index 100% rename from ui/css/index.css rename to ui/coreShared/css/index.css diff --git a/ui/css/tokens.css b/ui/coreShared/css/tokens.css similarity index 100% rename from ui/css/tokens.css rename to ui/coreShared/css/tokens.css diff --git a/ui/dev-server.ts b/ui/coreShared/dev-server.ts similarity index 99% rename from ui/dev-server.ts rename to ui/coreShared/dev-server.ts index c61a0b0eb..e17c7958c 100644 --- a/ui/dev-server.ts +++ b/ui/coreShared/dev-server.ts @@ -1,11 +1,10 @@ import * as http from 'node:http' import * as filesystem from 'node:fs/promises' import * as path from 'node:path' -import * as url from 'node:url' +import { getUiAppPaths, parseUiAppIdFromProcess, type UiAppId } from './build/appPaths.mts' -const directoryOfThisFile = path.dirname(url.fileURLToPath(import.meta.url)) -const uiRootDirectory = directoryOfThisFile -const repositoryRootDirectory = path.resolve(uiRootDirectory, '..') +const appId = parseUiAppIdFromProcess('the development server') +const { appRoot: uiRootDirectory, repositoryRoot: repositoryRootDirectory } = getUiAppPaths(appId) const liveReloadClients = new Set<http.ServerResponse>() const getServedFilePaths = (requestPath: string) => { @@ -110,7 +109,8 @@ server.on('request', async (request, response) => { }) // Initiate the server on `port` and print a message -const port = 12345 +const ports: Record<UiAppId, number> = { statoblast: 12347, trading: 4163, zoltar: 12346 } +const port = ports[appId] server.listen(port) server.on('listening', () => { const address = server.address() diff --git a/ui/favicon.ico b/ui/coreShared/favicon.ico similarity index 100% rename from ui/favicon.ico rename to ui/coreShared/favicon.ico diff --git a/ui/favicon.svg b/ui/coreShared/favicon.svg similarity index 100% rename from ui/favicon.svg rename to ui/coreShared/favicon.svg diff --git a/ui/coreShared/package.json b/ui/coreShared/package.json new file mode 100644 index 000000000..1399d499c --- /dev/null +++ b/ui/coreShared/package.json @@ -0,0 +1,58 @@ +{ + "description": "Shared UI primitives, shell framework, and simulation engine", + "name": "@zoltar/ui-core-shared", + "private": true, + "repository": {}, + "license": "Unlicense", + "type": "module", + "devDependencies": { + "better-typescript-lib": "2.11.0", + "bun-types": "1.3.11", + "typescript": "5.8.2", + "funtypes": "5.1.1" + }, + "dependencies": { + "@preact/signals": "2.0.1", + "@noble/curves": "2.2.0", + "@noble/hashes": "2.2.0", + "@zoltar/shared": "file:../../shared", + "buffer": "6.0.3", + "micro-eth-signer": "0.18.1", + "micro-packed": "0.8.0", + "preact": "10.26.4", + "source-map": "0.6.1", + "tevm": "1.0.0-rc.151" + }, + "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", + "brace-expansion": "5.0.9", + "fast-uri": "3.1.5", + "nanoid": "3.3.18", + "postcss": "8.5.23", + "tmp": "0.2.7", + "ws": "8.21.0" + }, + "scripts": { + "build:tests": "bun ./build/tests.mts coreShared", + "generate": "bun ./build/projectArtifacts.mts && bun ./build/vendor.mts zoltar && bun ./build/vendor.mts statoblast && bun ./build/vendor.mts trading", + "tsc": "bun x tsc --project tsconfig.json", + "build:workers": "bun ./build/workers.mts zoltar && bun ./build/workers.mts statoblast && bun ./build/workers.mts trading" + }, + "types": "./js/index.d.ts", + "exports": { + "./tests/simulationTestUtils.js": "./ts/tests/simulation/testUtils.ts", + "./tests/testUtils/domEnvironment.js": "./ts/tests/testUtils/domEnvironment.ts", + "./tests/testUtils/fakeBackend.js": "./ts/tests/testUtils/fakeBackend.ts", + "./tests/testUtils/protocolTestSupport.js": "./ts/tests/testUtils/protocolTestSupport.ts", + "./tests/testUtils/queries.js": "./ts/tests/testUtils/queries.ts", + "./tests/testUtils/renderIntoDocument.js": "./ts/tests/testUtils/renderIntoDocument.tsx", + "./tests/testUtils/transactionActionButton.js": "./ts/tests/testUtils/transactionActionButton.ts", + "./*": { + "bun": "./ts/*.ts", + "default": "./js/*.js" + }, + "./tests/testUtils/testRouting.js": "./ts/tests/testUtils/testRouting.ts" + } +} diff --git a/ui/scripts/docker-entrypoint.sh b/ui/coreShared/scripts/docker-entrypoint.sh similarity index 100% rename from ui/scripts/docker-entrypoint.sh rename to ui/coreShared/scripts/docker-entrypoint.sh diff --git a/ui/scripts/link-node-modules.mjs b/ui/coreShared/scripts/link-node-modules.mjs similarity index 100% rename from ui/scripts/link-node-modules.mjs rename to ui/coreShared/scripts/link-node-modules.mjs diff --git a/ui/ts/app/appRoot.ts b/ui/coreShared/ts/app/appRoot.ts similarity index 79% rename from ui/ts/app/appRoot.ts rename to ui/coreShared/ts/app/appRoot.ts index e473540c6..e05e1ec29 100644 --- a/ui/ts/app/appRoot.ts +++ b/ui/coreShared/ts/app/appRoot.ts @@ -2,11 +2,10 @@ import { createElement, render, type ComponentChildren } from 'preact' import * as appCopy from '../copy/app.js' import { getErrorMessage } from '../lib/errors.js' import { initializeActiveEnvironment } from '../lib/activeEnvironment.js' -import { App } from './App.js' import { AppErrorBoundary } from './components/AppErrorBoundary.js' import { ApplicationErrorNotice } from './components/ApplicationErrorNotice.js' -export function createAppRoot(children: ComponentChildren = createElement(App, {})) { +export function createAppRoot(children: ComponentChildren) { return createElement(AppErrorBoundary, {}, children) } @@ -16,9 +15,11 @@ type MountAppOptions = { target?: Element } -export async function mountApp(options: MountAppOptions = {}) { +export async function mountApp(options: MountAppOptions) { const initialize = options.initialize ?? initializeActiveEnvironment - const root = options.root ?? createAppRoot + const rootOption = options.root + if (rootOption === undefined) throw new Error('mountApp requires a root component factory') + const root = rootOption const target = options.target ?? document.body try { await initialize() diff --git a/ui/ts/app/components/AppErrorBoundary.tsx b/ui/coreShared/ts/app/components/AppErrorBoundary.tsx similarity index 100% rename from ui/ts/app/components/AppErrorBoundary.tsx rename to ui/coreShared/ts/app/components/AppErrorBoundary.tsx diff --git a/ui/coreShared/ts/app/components/AppHeaderShell.tsx b/ui/coreShared/ts/app/components/AppHeaderShell.tsx new file mode 100644 index 000000000..48cb844d2 --- /dev/null +++ b/ui/coreShared/ts/app/components/AppHeaderShell.tsx @@ -0,0 +1,58 @@ +import * as appCopy from '../../copy/app.js' +import { SimulationBanner } from '../../components/SimulationBanner.js' +import { TabNavigation } from '../../components/TabNavigation.js' +import type { SimulationController } from '../../simulation/controller.js' +import type { RouteTabDefinition } from '../../types/components.js' +import type { ComponentChildren } from 'preact' + +type AppHeaderShellProps = { + mainElementId?: string + header?: ComponentChildren + renderHeader?: (simulationBanner: ComponentChildren) => ComponentChildren + overview?: ComponentChildren + simulationController: SimulationController | undefined + subNavigation?: ComponentChildren + tabNavigation?: { + route: string + tabs: readonly RouteTabDefinition[] + onRouteChange: (route: string) => void + } + onEnvironmentChanged?: () => Promise<void> + onRefresh: () => Promise<void> +} + +export function AppHeaderShell({ mainElementId = 'app-content', header, renderHeader, overview, simulationController, subNavigation, tabNavigation, onEnvironmentChanged = async () => undefined, onRefresh }: AppHeaderShellProps) { + const focusAppContent = () => { + const appContent = document.getElementById(mainElementId) + if (!(appContent instanceof HTMLElement)) return + appContent.tabIndex = -1 + appContent.focus() + } + + const simulationBanner = simulationController === undefined ? undefined : <SimulationBanner controller={simulationController} onEnvironmentChanged={onEnvironmentChanged} onRefresh={onRefresh} /> + const shellHeader = header ?? ( + <div className='top-shell'> + <div className='top-shell-content'>{overview}</div> + <div className='app-nav-stack'> + {tabNavigation === undefined ? undefined : <TabNavigation {...tabNavigation} />} + {subNavigation} + </div> + </div> + ) + + return ( + <> + <button className='skip-link' type='button' onClick={focusAppContent}> + {appCopy.skipToMainContent} + </button> + {renderHeader === undefined ? ( + <> + {simulationBanner} + {shellHeader} + </> + ) : ( + renderHeader(simulationBanner) + )} + </> + ) +} diff --git a/ui/ts/app/components/AppPageHeading.tsx b/ui/coreShared/ts/app/components/AppPageHeading.tsx similarity index 71% rename from ui/ts/app/components/AppPageHeading.tsx rename to ui/coreShared/ts/app/components/AppPageHeading.tsx index 777ac5d00..799522295 100644 --- a/ui/ts/app/components/AppPageHeading.tsx +++ b/ui/coreShared/ts/app/components/AppPageHeading.tsx @@ -1,11 +1,12 @@ import { useEffect, useRef } from 'preact/hooks' -import { formatAppDocumentTitle } from '../lib/appPageTitle.js' type AppPageHeadingProps = { + mainElementId?: string + formatDocumentTitle: (pageTitle: string) => string pageTitle: string } -export function AppPageHeading({ pageTitle }: AppPageHeadingProps) { +export function AppPageHeading({ mainElementId = 'app-content', formatDocumentTitle, pageTitle }: AppPageHeadingProps) { const headingRef = useRef<HTMLHeadingElement>(null) const previousPageTitleRef = useRef(pageTitle) const historyTraversalUrlRef = useRef<string | undefined>() @@ -19,7 +20,7 @@ export function AppPageHeading({ pageTitle }: AppPageHeadingProps) { }, []) useEffect(() => { - document.title = formatAppDocumentTitle(pageTitle) + document.title = formatDocumentTitle(pageTitle) if (previousPageTitleRef.current === pageTitle) return previousPageTitleRef.current = pageTitle const heading = headingRef.current @@ -27,9 +28,9 @@ export function AppPageHeading({ pageTitle }: AppPageHeadingProps) { const wasHistoryTraversal = historyTraversalUrlRef.current === window.location.href historyTraversalUrlRef.current = undefined - if (!wasHistoryTraversal) document.getElementById('app-content')?.scrollIntoView({ block: 'start' }) + if (!wasHistoryTraversal) document.getElementById(mainElementId)?.scrollIntoView({ block: 'start' }) heading.focus({ preventScroll: true }) - }, [pageTitle]) + }, [formatDocumentTitle, mainElementId, pageTitle]) return ( <h1 ref={headingRef} className='visually-hidden' tabIndex={-1}> diff --git a/ui/ts/app/components/AppStatusNotices.tsx b/ui/coreShared/ts/app/components/AppStatusNotices.tsx similarity index 93% rename from ui/ts/app/components/AppStatusNotices.tsx rename to ui/coreShared/ts/app/components/AppStatusNotices.tsx index 1c994aad2..d22c090a2 100644 --- a/ui/ts/app/components/AppStatusNotices.tsx +++ b/ui/coreShared/ts/app/components/AppStatusNotices.tsx @@ -12,7 +12,7 @@ type AppStatusNoticesProps = { readBackendMessage: string | undefined readBackendStatus?: ReadBackendStatus | undefined simulationBootstrapError: string | undefined - showAugurStatoblastDeploymentWarning: boolean + showApplicationDeploymentWarning: boolean zoltarUniverseError?: string | undefined } @@ -65,11 +65,11 @@ function buildRpcOverrideNotice(readBackendStatus: ReadBackendStatus | undefined } } -export function AppStatusNotices({ errorMessage, errorMessages = [], loadingZoltarUniverse = false, onRetryZoltarUniverse, readBackendMessage, readBackendStatus, simulationBootstrapError, showAugurStatoblastDeploymentWarning, zoltarUniverseError }: AppStatusNoticesProps) { +export function AppStatusNotices({ errorMessage, errorMessages = [], loadingZoltarUniverse = false, onRetryZoltarUniverse, readBackendMessage, readBackendStatus, simulationBootstrapError, showApplicationDeploymentWarning, zoltarUniverseError }: AppStatusNoticesProps) { const items: NoticeItem[] = [] const rpcOverrideNotice = buildRpcOverrideNotice(readBackendStatus) if (simulationBootstrapError !== undefined) items.push({ detail: simulationBootstrapError, id: 'simulation-bootstrap-error', tone: 'blocking', title: appCopy.simulationBootstrapFailed }) - if (showAugurStatoblastDeploymentWarning) items.push({ detail: appCopy.deploymentIncompleteReason, id: 'setup-incomplete', tone: 'blocking', title: appCopy.setupIncomplete }) + if (showApplicationDeploymentWarning) items.push({ detail: appCopy.deploymentIncompleteReason, id: 'setup-incomplete', tone: 'blocking', title: appCopy.setupIncomplete }) if (readBackendMessage !== undefined) items.push({ detail: getReadBackendNoticeDetail(readBackendMessage), id: 'read-backend-mismatch', tone: 'blocking', title: appCopy.readRpcMismatch }) if (zoltarUniverseError !== undefined) items.push({ diff --git a/ui/ts/app/components/ApplicationErrorNotice.tsx b/ui/coreShared/ts/app/components/ApplicationErrorNotice.tsx similarity index 100% rename from ui/ts/app/components/ApplicationErrorNotice.tsx rename to ui/coreShared/ts/app/components/ApplicationErrorNotice.tsx diff --git a/ui/ts/app/components/GlobalTransactionTray.tsx b/ui/coreShared/ts/app/components/GlobalTransactionTray.tsx similarity index 98% rename from ui/ts/app/components/GlobalTransactionTray.tsx rename to ui/coreShared/ts/app/components/GlobalTransactionTray.tsx index 8e6e49290..ecccab0b7 100644 --- a/ui/ts/app/components/GlobalTransactionTray.tsx +++ b/ui/coreShared/ts/app/components/GlobalTransactionTray.tsx @@ -2,9 +2,12 @@ import { useEffect, useRef, useState } from 'preact/hooks' import * as appCopy from '../../copy/app.js' import { TransactionPresentationNotice } from '../../components/TransactionPresentationNotice.js' import { WarningSurface } from '../../components/WarningSurface.js' -import { formatUniverseIdHex } from '../../features/universes/lib/universe.js' import type { GlobalTransactionPresentation } from '../../types/components.js' +function formatUniverseIdHex(universeId: bigint) { + return `0x${universeId.toString(16)}` +} + type GlobalTransactionTrayProps = { activeUniverseId?: bigint | undefined routeKey?: string diff --git a/ui/ts/app/components/NotFoundSection.tsx b/ui/coreShared/ts/app/components/NotFoundSection.tsx similarity index 100% rename from ui/ts/app/components/NotFoundSection.tsx rename to ui/coreShared/ts/app/components/NotFoundSection.tsx diff --git a/ui/ts/app/components/RouteSubNavigation.tsx b/ui/coreShared/ts/app/components/RouteSubNavigation.tsx similarity index 100% rename from ui/ts/app/components/RouteSubNavigation.tsx rename to ui/coreShared/ts/app/components/RouteSubNavigation.tsx diff --git a/ui/ts/app/hooks/useHashRoute.ts b/ui/coreShared/ts/app/hooks/useHashRoute.ts similarity index 82% rename from ui/ts/app/hooks/useHashRoute.ts rename to ui/coreShared/ts/app/hooks/useHashRoute.ts index bcb849d8a..e352c92e5 100644 --- a/ui/ts/app/hooks/useHashRoute.ts +++ b/ui/coreShared/ts/app/hooks/useHashRoute.ts @@ -1,12 +1,13 @@ import { useSignal } from '@preact/signals' import { useEffect } from 'preact/hooks' import { buildRouteHref, ensureRouteHash, getCurrentRoute, getRouteHash, getTopLevelRouteSearch } from '../../lib/routing.js' -import type { Route } from '../../types/app.js' + +type Route = string export function useHashRoute() { const route = useSignal<Route>(getCurrentRoute()) - const navigate = (nextRoute: Exclude<Route, 'not-found'>, preservedParameters: ReadonlySet<string> = new Set()) => { + const navigate = (nextRoute: Route, preservedParameters: ReadonlySet<string> = new Set()) => { window.location.hash = buildRouteHref(getRouteHash(nextRoute), getTopLevelRouteSearch(nextRoute, undefined, preservedParameters)) } diff --git a/ui/ts/app/hooks/useOnchainState.ts b/ui/coreShared/ts/app/hooks/useOnchainState.ts similarity index 96% rename from ui/ts/app/hooks/useOnchainState.ts rename to ui/coreShared/ts/app/hooks/useOnchainState.ts index f140f9ca0..088fcb0a9 100644 --- a/ui/ts/app/hooks/useOnchainState.ts +++ b/ui/coreShared/ts/app/hooks/useOnchainState.ts @@ -1,16 +1,14 @@ import { useSignal } from '@preact/signals' import { useEffect, useLayoutEffect, useRef } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { getDeploymentSteps, loadDeploymentStatusOracleSnapshot, loadErc20Balance } from '../../protocol/index.js' import { createConnectedReadClient, normalizeAccount } from '../../lib/clients.js' import type { ChainBackend, ReadBackendStatus } from '../../lib/chainBackend.js' import { getErrorMessage, hasErrorCode, hasErrorMessage } from '../../lib/errors.js' import { getActiveBackend } from '../../lib/activeEnvironment.js' import { getNetworkSwitchTarget, getPublicNetworkProfileForChainId } from '../../lib/networkProfile.js' import { useRequestGuard } from '../../lib/requestGuard.js' -import { getWethAddress } from '../../protocol/uniswapQuoter.js' import type { AccountState, RefreshStateOptions } from '../../types/app.js' -import type { DeploymentStatus } from '../../types/contracts.js' +import type { DeploymentStatus, DeploymentStep, ReadClient } from '../../types/contracts.js' import { useLoadController } from '../../hooks/useLoadController.js' import { sameChainId } from '../../lib/chainId.js' import { formatTimestampWithRelative } from '../../lib/formatters.js' @@ -160,18 +158,13 @@ type UseOnchainStateOptions = { } export type UseOnchainStateDependencies = { - getDeploymentSteps: typeof getDeploymentSteps - loadDeploymentStatusOracleSnapshot: typeof loadDeploymentStatusOracleSnapshot - loadErc20Balance: typeof loadErc20Balance + getDeploymentSteps: () => ReadonlyArray<DeploymentStep> + getWethAddress: () => Address + loadDeploymentStatusOracleSnapshot: (readClient: ReadClient) => Promise<{ applicationDeploymentComplete: boolean; deploymentStatuses: DeploymentStatus[] }> + loadErc20Balance: (readClient: ReadClient, tokenAddress: Address, accountAddress: Address) => Promise<bigint> } -const defaultUseOnchainStateDependencies: UseOnchainStateDependencies = { - getDeploymentSteps, - loadDeploymentStatusOracleSnapshot, - loadErc20Balance, -} - -export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = true, onSupportedNetworkChange }: UseOnchainStateOptions = {}, dependencies: UseOnchainStateDependencies = defaultUseOnchainStateDependencies) { +export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = true, onSupportedNetworkChange }: UseOnchainStateOptions = {}, dependencies: UseOnchainStateDependencies) { const accountState = useSignal<AccountState>({ address: undefined, chainId: undefined, @@ -188,7 +181,7 @@ export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = const walletStateLoad = useLoadController() const deploymentStatusLoad = useLoadController() const deploymentStatusesLoaded = useSignal(false) - const augurStatoblastDeployed = useSignal<boolean | undefined>(undefined) + const applicationDeploymentComplete = useSignal<boolean | undefined>(undefined) const currentTimestamp = useSignal<bigint | undefined>(getActiveBackend().currentTimestamp) const currentBlockNumber = useSignal<bigint | undefined>(undefined) const environmentBootstrapError = useSignal<string | undefined>(undefined) @@ -239,7 +232,7 @@ export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = const setDeploymentStatuses = (update: (current: DeploymentStatus[]) => DeploymentStatus[]) => { const updated = update(deploymentStatuses.value) deploymentStatuses.value = updated - if (updated.every(step => step.deployed)) augurStatoblastDeployed.value = true + if (updated.every(step => step.deployed)) applicationDeploymentComplete.value = true } const invalidateDeploymentState = () => { deploymentStatuses.value = dependencies.getDeploymentSteps().map(step => ({ @@ -247,7 +240,7 @@ export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = deployed: false, })) deploymentStatusesLoaded.value = false - augurStatoblastDeployed.value = undefined + applicationDeploymentComplete.value = undefined } const refreshChainClock = (backend: ChainBackend) => { const activeRequest = chainClockRefreshRef.current @@ -435,7 +428,7 @@ export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = try { const snapshot = await dependencies.loadDeploymentStatusOracleSnapshot(backend.createReadClient()) if (!isCurrent()) return - augurStatoblastDeployed.value = snapshot.augurStatoblastDeployed + applicationDeploymentComplete.value = snapshot.applicationDeploymentComplete deploymentStatuses.value = snapshot.deploymentStatuses deploymentStatusesLoaded.value = true } catch (error) { @@ -464,7 +457,7 @@ export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = if (connectedAddress !== undefined && walletOnExpectedChain) { const readClient = createConnectedReadClient() const ethBalanceAttoEthPromise = readClient.getBalance({ address: connectedAddress }) - const wethBalanceAttoEthPromise = dependencies.loadErc20Balance(readClient, getWethAddress(), connectedAddress) + const wethBalanceAttoEthPromise = dependencies.loadErc20Balance(readClient, dependencies.getWethAddress(), connectedAddress) void loadWalletState({ chainIdPromise: Promise.resolve(connectedChainId ?? backend.profile.chainIdHex), connectedAddress, @@ -687,7 +680,7 @@ export function useOnchainState({ activeEnvironmentNonce = 0, enableChainClock = isManagingWallet: isManagingWallet.value, isLoadingDeploymentStatuses: deploymentStatusLoad.isLoading.value, isRefreshing: walletStateLoad.isLoading.value, - augurStatoblastDeployed: augurStatoblastDeployed.value, + applicationDeploymentComplete: applicationDeploymentComplete.value, refreshState, setDeploymentStatuses, disconnectWallet, diff --git a/ui/ts/app/hooks/useUrlState.ts b/ui/coreShared/ts/app/hooks/useUrlState.ts similarity index 100% rename from ui/ts/app/hooks/useUrlState.ts rename to ui/coreShared/ts/app/hooks/useUrlState.ts diff --git a/ui/coreShared/ts/app/lib/appRouteGate.ts b/ui/coreShared/ts/app/lib/appRouteGate.ts new file mode 100644 index 000000000..381ff7f5b --- /dev/null +++ b/ui/coreShared/ts/app/lib/appRouteGate.ts @@ -0,0 +1,3 @@ +export function shouldRenderAppRouteContent(route: string, readBackendMessage: string | undefined, deploymentRoute = 'deploy') { + return route === deploymentRoute || readBackendMessage === undefined +} diff --git a/ui/ts/app/lib/supportedNetworkChange.ts b/ui/coreShared/ts/app/lib/supportedNetworkChange.ts similarity index 100% rename from ui/ts/app/lib/supportedNetworkChange.ts rename to ui/coreShared/ts/app/lib/supportedNetworkChange.ts diff --git a/ui/ts/components/ActionLauncherButton.tsx b/ui/coreShared/ts/components/ActionLauncherButton.tsx similarity index 100% rename from ui/ts/components/ActionLauncherButton.tsx rename to ui/coreShared/ts/components/ActionLauncherButton.tsx diff --git a/ui/ts/components/ActionLauncherCard.tsx b/ui/coreShared/ts/components/ActionLauncherCard.tsx similarity index 100% rename from ui/ts/components/ActionLauncherCard.tsx rename to ui/coreShared/ts/components/ActionLauncherCard.tsx diff --git a/ui/ts/components/AddressInfo.tsx b/ui/coreShared/ts/components/AddressInfo.tsx similarity index 100% rename from ui/ts/components/AddressInfo.tsx rename to ui/coreShared/ts/components/AddressInfo.tsx diff --git a/ui/ts/components/AddressValue.tsx b/ui/coreShared/ts/components/AddressValue.tsx similarity index 80% rename from ui/ts/components/AddressValue.tsx rename to ui/coreShared/ts/components/AddressValue.tsx index 7cd9937bf..266280410 100644 --- a/ui/ts/components/AddressValue.tsx +++ b/ui/coreShared/ts/components/AddressValue.tsx @@ -27,9 +27,7 @@ function AddressText({ address, responsiveAbbreviation }: { address: string; res ) } -export function AddressValue({ address, className = '', copyable = true, responsiveAbbreviation = false }: AddressValueProps) { - const { copied, copyError, copyErrorId, copyText } = useCopyToClipboard(address) - +export function ReadOnlyAddressValue({ address, className = '', responsiveAbbreviation = false }: Omit<AddressValueProps, 'copyable'>) { if (address === undefined) { const placeholder = getMetricPlaceholderPresentation(address)?.placeholder return ( @@ -38,13 +36,17 @@ export function AddressValue({ address, className = '', copyable = true, respons </span> ) } + return ( + <span className={`address-value ${className}`} title={address}> + <AddressText address={address} responsiveAbbreviation={responsiveAbbreviation} /> + </span> + ) +} - if (!copyable) - return ( - <span className={`address-value ${className}`} title={address}> - <AddressText address={address} responsiveAbbreviation={responsiveAbbreviation} /> - </span> - ) +export function AddressValue({ address, className = '', copyable = true, responsiveAbbreviation = false }: AddressValueProps) { + const { copied, copyError, copyErrorId, copyText } = useCopyToClipboard(address) + + if (address === undefined || !copyable) return <ReadOnlyAddressValue address={address} className={className} responsiveAbbreviation={responsiveAbbreviation} /> return ( <span className='copy-value-wrap'> diff --git a/ui/ts/components/ApprovedAmountValue.tsx b/ui/coreShared/ts/components/ApprovedAmountValue.tsx similarity index 100% rename from ui/ts/components/ApprovedAmountValue.tsx rename to ui/coreShared/ts/components/ApprovedAmountValue.tsx diff --git a/ui/ts/components/Badge.tsx b/ui/coreShared/ts/components/Badge.tsx similarity index 100% rename from ui/ts/components/Badge.tsx rename to ui/coreShared/ts/components/Badge.tsx diff --git a/ui/ts/components/ComparisonRecord.tsx b/ui/coreShared/ts/components/ComparisonRecord.tsx similarity index 100% rename from ui/ts/components/ComparisonRecord.tsx rename to ui/coreShared/ts/components/ComparisonRecord.tsx diff --git a/ui/ts/components/CopyErrorMessage.tsx b/ui/coreShared/ts/components/CopyErrorMessage.tsx similarity index 100% rename from ui/ts/components/CopyErrorMessage.tsx rename to ui/coreShared/ts/components/CopyErrorMessage.tsx diff --git a/ui/ts/components/CurrencyValue.tsx b/ui/coreShared/ts/components/CurrencyValue.tsx similarity index 100% rename from ui/ts/components/CurrencyValue.tsx rename to ui/coreShared/ts/components/CurrencyValue.tsx diff --git a/ui/ts/components/DataGrid.tsx b/ui/coreShared/ts/components/DataGrid.tsx similarity index 100% rename from ui/ts/components/DataGrid.tsx rename to ui/coreShared/ts/components/DataGrid.tsx diff --git a/ui/ts/components/EntityCard.tsx b/ui/coreShared/ts/components/EntityCard.tsx similarity index 100% rename from ui/ts/components/EntityCard.tsx rename to ui/coreShared/ts/components/EntityCard.tsx diff --git a/ui/ts/components/EnumDropdown.tsx b/ui/coreShared/ts/components/EnumDropdown.tsx similarity index 100% rename from ui/ts/components/EnumDropdown.tsx rename to ui/coreShared/ts/components/EnumDropdown.tsx diff --git a/ui/ts/components/ErrorNotice.tsx b/ui/coreShared/ts/components/ErrorNotice.tsx similarity index 100% rename from ui/ts/components/ErrorNotice.tsx rename to ui/coreShared/ts/components/ErrorNotice.tsx diff --git a/ui/ts/components/FormInput.tsx b/ui/coreShared/ts/components/FormInput.tsx similarity index 100% rename from ui/ts/components/FormInput.tsx rename to ui/coreShared/ts/components/FormInput.tsx diff --git a/ui/ts/components/GlobalTransactionPresentationContext.tsx b/ui/coreShared/ts/components/GlobalTransactionPresentationContext.tsx similarity index 100% rename from ui/ts/components/GlobalTransactionPresentationContext.tsx rename to ui/coreShared/ts/components/GlobalTransactionPresentationContext.tsx diff --git a/ui/ts/components/IdentifierValue.tsx b/ui/coreShared/ts/components/IdentifierValue.tsx similarity index 100% rename from ui/ts/components/IdentifierValue.tsx rename to ui/coreShared/ts/components/IdentifierValue.tsx diff --git a/ui/ts/features/security-pools/components/LifecycleStageBanner.tsx b/ui/coreShared/ts/components/LifecycleStageBanner.tsx similarity index 88% rename from ui/ts/features/security-pools/components/LifecycleStageBanner.tsx rename to ui/coreShared/ts/components/LifecycleStageBanner.tsx index d834ab9a0..5eae1c656 100644 --- a/ui/ts/features/security-pools/components/LifecycleStageBanner.tsx +++ b/ui/coreShared/ts/components/LifecycleStageBanner.tsx @@ -1,7 +1,7 @@ -import * as commonCopy from '../../../copy/common.js' -import type { LifecycleStagePresentation } from '../../types.js' -import { LoadingAwareText } from '../../../components/LoadingText.js' -import { WarningSurface } from '../../../components/WarningSurface.js' +import * as commonCopy from '../copy/common.js' +import type { LifecycleStagePresentation } from '../types/components.js' +import { LoadingAwareText } from '../components/LoadingText.js' +import { WarningSurface } from '../components/WarningSurface.js' type LifecycleStageBannerProps = { detailId?: string | undefined diff --git a/ui/ts/components/LoadableValue.tsx b/ui/coreShared/ts/components/LoadableValue.tsx similarity index 100% rename from ui/ts/components/LoadableValue.tsx rename to ui/coreShared/ts/components/LoadableValue.tsx diff --git a/ui/ts/components/LoadingText.tsx b/ui/coreShared/ts/components/LoadingText.tsx similarity index 100% rename from ui/ts/components/LoadingText.tsx rename to ui/coreShared/ts/components/LoadingText.tsx diff --git a/ui/ts/components/LookupFieldRow.tsx b/ui/coreShared/ts/components/LookupFieldRow.tsx similarity index 100% rename from ui/ts/components/LookupFieldRow.tsx rename to ui/coreShared/ts/components/LookupFieldRow.tsx diff --git a/ui/ts/components/MetricField.tsx b/ui/coreShared/ts/components/MetricField.tsx similarity index 100% rename from ui/ts/components/MetricField.tsx rename to ui/coreShared/ts/components/MetricField.tsx diff --git a/ui/ts/components/MetricGrid.tsx b/ui/coreShared/ts/components/MetricGrid.tsx similarity index 100% rename from ui/ts/components/MetricGrid.tsx rename to ui/coreShared/ts/components/MetricGrid.tsx diff --git a/ui/ts/components/NoticeStack.tsx b/ui/coreShared/ts/components/NoticeStack.tsx similarity index 100% rename from ui/ts/components/NoticeStack.tsx rename to ui/coreShared/ts/components/NoticeStack.tsx diff --git a/ui/ts/components/OperationModal.tsx b/ui/coreShared/ts/components/OperationModal.tsx similarity index 100% rename from ui/ts/components/OperationModal.tsx rename to ui/coreShared/ts/components/OperationModal.tsx diff --git a/ui/ts/features/markets/components/OutcomeChipRow.tsx b/ui/coreShared/ts/components/OutcomeChipRow.tsx similarity index 83% rename from ui/ts/features/markets/components/OutcomeChipRow.tsx rename to ui/coreShared/ts/components/OutcomeChipRow.tsx index 4b7479a01..d61b7992c 100644 --- a/ui/ts/features/markets/components/OutcomeChipRow.tsx +++ b/ui/coreShared/ts/components/OutcomeChipRow.tsx @@ -1,4 +1,4 @@ -import type { OutcomeChipRowProps } from '../../types.js' +import type { OutcomeChipRowProps } from '../types/components.js' export function OutcomeChipRow({ className = '', items }: OutcomeChipRowProps) { return ( diff --git a/ui/ts/features/markets/components/OutcomeSelectionList.tsx b/ui/coreShared/ts/components/OutcomeSelectionList.tsx similarity index 91% rename from ui/ts/features/markets/components/OutcomeSelectionList.tsx rename to ui/coreShared/ts/components/OutcomeSelectionList.tsx index 647b4bd12..833b4adf1 100644 --- a/ui/ts/features/markets/components/OutcomeSelectionList.tsx +++ b/ui/coreShared/ts/components/OutcomeSelectionList.tsx @@ -1,4 +1,4 @@ -import type { OutcomeSelectionListProps } from '../../types.js' +import type { OutcomeSelectionListProps } from '../types/components.js' export function OutcomeSelectionList({ className = '', emptyMessage, items }: OutcomeSelectionListProps) { if (items.length === 0) return emptyMessage === undefined ? undefined : <p className='detail'>{emptyMessage}</p> diff --git a/ui/ts/components/PaginationControls.tsx b/ui/coreShared/ts/components/PaginationControls.tsx similarity index 100% rename from ui/ts/components/PaginationControls.tsx rename to ui/coreShared/ts/components/PaginationControls.tsx diff --git a/ui/ts/components/ProgressMeter.tsx b/ui/coreShared/ts/components/ProgressMeter.tsx similarity index 100% rename from ui/ts/components/ProgressMeter.tsx rename to ui/coreShared/ts/components/ProgressMeter.tsx diff --git a/ui/ts/features/markets/components/Question.tsx b/ui/coreShared/ts/components/Question.tsx similarity index 78% rename from ui/ts/features/markets/components/Question.tsx rename to ui/coreShared/ts/components/Question.tsx index febb9e307..520cc8528 100644 --- a/ui/ts/features/markets/components/Question.tsx +++ b/ui/coreShared/ts/components/Question.tsx @@ -1,15 +1,16 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { IdentifierValue } from '../../../components/IdentifierValue.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' +import * as commonCopy from '../copy/common.js' + +import { LoadingText } from '../components/LoadingText.js' +import { IdentifierValue } from '../components/IdentifierValue.js' +import { MetricGrid } from '../components/MetricGrid.js' +import { MetricField } from '../components/MetricField.js' import { OutcomeChipRow } from './OutcomeChipRow.js' -import { TimestampValue } from '../../../components/TimestampValue.js' +import { TimestampValue } from '../components/TimestampValue.js' import { appendInvalidOutcomeLabelIfMissing, isInvalidOutcomeLabel } from '../lib/outcomeLabels.js' import { getMarketTypeLabel } from '../lib/marketType.js' +import * as marketTypeCopy from '../copy/marketType.js' import { formatScalarDisplayValue } from '../lib/scalarOutcome.js' -import type { MarketDetails } from '../../../types/contracts.js' +import type { MarketDetails } from '../types/contracts.js' type QuestionProps = { className?: string @@ -37,7 +38,7 @@ type QuestionSummaryField = } export function getQuestionTitle(question: MarketDetails) { - return question.title.trim() === '' ? marketCopy.untitledQuestion : question.title + return question.title.trim() === '' ? commonCopy.untitledQuestion : question.title } function getQuestionDescription(question: MarketDetails) { @@ -47,7 +48,7 @@ function getQuestionDescription(question: MarketDetails) { } function getDisplayedOutcomes(question: MarketDetails) { - const outcomes = question.outcomeLabels.length === 0 ? [marketCopy.scalar] : question.outcomeLabels + const outcomes = question.outcomeLabels.length === 0 ? [marketTypeCopy.scalar] : question.outcomeLabels return appendInvalidOutcomeLabelIfMissing(outcomes) } @@ -58,15 +59,15 @@ function getDisplayRange(question: MarketDetails) { export function getQuestionSummaryFields(question: MarketDetails): QuestionSummaryField[] { const fields: QuestionSummaryField[] = [ - { kind: 'text', label: marketCopy.questionType, value: getMarketTypeLabel(question.marketType) }, + { kind: 'text', label: commonCopy.questionType, value: getMarketTypeLabel(question.marketType) }, { kind: 'identifier', label: commonCopy.questionId, value: question.questionId }, - { kind: 'timestamp', label: marketCopy.created, value: question.createdAt }, - { kind: 'timestamp', label: marketCopy.endTime, value: question.endTime }, - { kind: 'text', label: marketCopy.outcomes, value: getDisplayedOutcomes(question).join(', ') }, + { kind: 'timestamp', label: commonCopy.created, value: question.createdAt }, + { kind: 'timestamp', label: commonCopy.endTime, value: question.endTime }, + { kind: 'text', label: commonCopy.outcomes, value: getDisplayedOutcomes(question).join(', ') }, ] if (question.marketType === 'scalar') - fields.push({ kind: 'text', label: marketCopy.ticks, value: question.numTicks.toString() }, { kind: 'text', label: marketCopy.displayRange, value: getDisplayRange(question) }, { kind: 'text', label: marketCopy.answerUnit, value: question.answerUnit === '' ? commonCopy.none : question.answerUnit }) + fields.push({ kind: 'text', label: commonCopy.ticks, value: question.numTicks.toString() }, { kind: 'text', label: commonCopy.displayRange, value: getDisplayRange(question) }, { kind: 'text', label: commonCopy.answerUnit, value: question.answerUnit === '' ? commonCopy.none : question.answerUnit }) return fields } @@ -97,7 +98,7 @@ export function Question({ className = '', loading = false, question, showTitle return ( <div className={`question-summary ${className}`}> <p className='detail'> - <LoadingText>{marketCopy.questionDetailsLoadingLabel}</LoadingText> + <LoadingText>{commonCopy.questionDetailsLoadingLabel}</LoadingText> </p> </div> ) @@ -117,11 +118,11 @@ export function Question({ className = '', loading = false, question, showTitle ? [] : [ { - label: marketCopy.ticks, + label: commonCopy.ticks, value: question.numTicks.toString(), }, { - label: marketCopy.displayRange, + label: commonCopy.displayRange, value: getDisplayRange(question), }, ] @@ -136,15 +137,15 @@ export function Question({ className = '', loading = false, question, showTitle </div> )} <OutcomeChipRow items={outcomeItems} /> - <div className='question-preview-timeline' role='list' aria-label={marketCopy.questionTimeline}> + <div className='question-preview-timeline' role='list' aria-label={commonCopy.questionTimeline}> <div className='question-preview-timeline-item' role='listitem'> - <span className='question-preview-timeline-label'>{marketCopy.created}</span> + <span className='question-preview-timeline-label'>{commonCopy.created}</span> <strong className='question-preview-timeline-value'> <TimestampValue timestamp={question.createdAt} /> </strong> </div> <div className='question-preview-timeline-item' role='listitem'> - <span className='question-preview-timeline-label'>{marketCopy.endTime}</span> + <span className='question-preview-timeline-label'>{commonCopy.endTime}</span> <strong className='question-preview-timeline-value'> <TimestampValue timestamp={question.endTime} /> </strong> diff --git a/ui/ts/components/RankedBarList.tsx b/ui/coreShared/ts/components/RankedBarList.tsx similarity index 100% rename from ui/ts/components/RankedBarList.tsx rename to ui/coreShared/ts/components/RankedBarList.tsx diff --git a/ui/ts/components/ReadOnlyDetailAccordion.tsx b/ui/coreShared/ts/components/ReadOnlyDetailAccordion.tsx similarity index 100% rename from ui/ts/components/ReadOnlyDetailAccordion.tsx rename to ui/coreShared/ts/components/ReadOnlyDetailAccordion.tsx diff --git a/ui/ts/components/RequirementsChecklist.tsx b/ui/coreShared/ts/components/RequirementsChecklist.tsx similarity index 100% rename from ui/ts/components/RequirementsChecklist.tsx rename to ui/coreShared/ts/components/RequirementsChecklist.tsx diff --git a/ui/ts/components/RouteHeader.tsx b/ui/coreShared/ts/components/RouteHeader.tsx similarity index 100% rename from ui/ts/components/RouteHeader.tsx rename to ui/coreShared/ts/components/RouteHeader.tsx diff --git a/ui/ts/components/RouteWorkflowPanel.tsx b/ui/coreShared/ts/components/RouteWorkflowPanel.tsx similarity index 100% rename from ui/ts/components/RouteWorkflowPanel.tsx rename to ui/coreShared/ts/components/RouteWorkflowPanel.tsx diff --git a/ui/ts/features/markets/components/ScalarOutcomePicker.tsx b/ui/coreShared/ts/components/ScalarOutcomePicker.tsx similarity index 72% rename from ui/ts/features/markets/components/ScalarOutcomePicker.tsx rename to ui/coreShared/ts/components/ScalarOutcomePicker.tsx index 1b9802375..92d8047b6 100644 --- a/ui/ts/features/markets/components/ScalarOutcomePicker.tsx +++ b/ui/coreShared/ts/components/ScalarOutcomePicker.tsx @@ -1,28 +1,31 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { FormInput } from '../../../components/FormInput.js' -import { MetricField } from '../../../components/MetricField.js' -import { tryParseBigIntInput } from '../lib/marketForm.js' -import type { ScalarOutcomePickerProps } from '../../types.js' +import * as commonCopy from '../copy/common.js' + +import { DataGrid } from '../components/DataGrid.js' +import { FormInput } from '../components/FormInput.js' +import { MetricField } from '../components/MetricField.js' +import { tryParseBigIntInput } from '../lib/integerInput.js' +import type { ScalarOutcomePickerProps } from '../types/components.js' import { MAX_PRECISE_SCALAR_TICK_COUNT, clampScalarTickIndex, getScalarSliderFillWidth } from '../lib/scalarOutcome.js' import { useEffect, useId, useState } from 'preact/hooks' -import { tryParseDecimalInput } from '../../../lib/decimal.js' +import type * as preact from 'preact' +import { tryParseDecimalInput } from '../lib/decimal.js' import { formatScalarDisplayValue, getScalarDisplayValue, getScalarTickIndexForDisplayValue } from '@zoltar/shared/scalarOutcome' function getSafeSelectedTickValue(selectedTick: string) { return selectedTick.trim() === '' ? 0n : (tryParseBigIntInput(selectedTick) ?? 0n) } -export function ScalarOutcomePicker({ action, details, disabled = false, isInvalid, label, onInvalidChange, onSelectedTickChange, selectedOutcomeLabel, selectedTick, selectedTickLabel, showMinMax = true }: ScalarOutcomePickerProps) { +export function ScalarOutcomePicker({ action, clampExactTickInput = true, details, disabled = false, isInvalid, label, onInvalidChange, onSelectedTickChange, selectedOutcomeLabel, selectedTick, selectedTickLabel, showMinMax = true }: ScalarOutcomePickerProps) { const sliderLabelId = useId() const scalarValueErrorId = useId() - const selectedTickValue = clampScalarTickIndex(getSafeSelectedTickValue(selectedTick), details.numTicks) - const resolvedSelectedTick = selectedTickValue.toString() + const rawSelectedTickValue = getSafeSelectedTickValue(selectedTick) + const selectedTickIsInRange = rawSelectedTickValue >= 0n && rawSelectedTickValue <= details.numTicks + const selectedTickValue = clampScalarTickIndex(rawSelectedTickValue, details.numTicks) const canUseNativeSlider = details.numTicks <= MAX_PRECISE_SCALAR_TICK_COUNT + const resolvedSelectedTick = (!canUseNativeSlider && !clampExactTickInput ? rawSelectedTickValue : selectedTickValue).toString() const [exactTickInputValue, setExactTickInputValue] = useState(resolvedSelectedTick) const scalarQuestionDetails = details.displayValueMin === undefined || details.displayValueMax === undefined ? undefined : { answerUnit: details.answerUnit ?? '', displayValueMax: details.displayValueMax, displayValueMin: details.displayValueMin, numTicks: details.numTicks } - const selectedScalarValue = scalarQuestionDetails === undefined ? undefined : getScalarDisplayValue(scalarQuestionDetails, selectedTickValue) + const selectedScalarValue = scalarQuestionDetails === undefined || (!clampExactTickInput && !selectedTickIsInRange) ? undefined : getScalarDisplayValue(scalarQuestionDetails, selectedTickValue) const resolvedScalarValueInput = selectedScalarValue === undefined ? undefined : formatScalarDisplayValue(selectedScalarValue) const [scalarValueInput, setScalarValueInput] = useState(resolvedScalarValueInput ?? '') const [scalarValueError, setScalarValueError] = useState<string | undefined>(undefined) @@ -38,12 +41,12 @@ export function ScalarOutcomePicker({ action, details, disabled = false, isInval setScalarValueInput(value) const parsedValue = tryParseDecimalInput(value) if (parsedValue === undefined || scalarQuestionDetails === undefined) { - setScalarValueError(marketCopy.scalarValueInvalid) + setScalarValueError(commonCopy.scalarValueInvalid) return } const tickIndex = getScalarTickIndexForDisplayValue(scalarQuestionDetails, parsedValue) if (tickIndex === undefined) { - setScalarValueError(marketCopy.scalarValueInvalid) + setScalarValueError(commonCopy.scalarValueInvalid) return } setScalarValueError(undefined) @@ -81,16 +84,16 @@ export function ScalarOutcomePicker({ action, details, disabled = false, isInval inputMode='numeric' value={exactTickInputValue} onBlur={() => setExactTickInputValue(resolvedSelectedTick)} - onInput={event => { + onInput={(event: preact.JSX.TargetedEvent<HTMLInputElement>) => { const nextInputValue = event.currentTarget.value setExactTickInputValue(nextInputValue) const parsedTick = tryParseBigIntInput(nextInputValue) if (parsedTick === undefined) return - onSelectedTickChange(clampScalarTickIndex(parsedTick, details.numTicks).toString()) + onSelectedTickChange((clampExactTickInput ? clampScalarTickIndex(parsedTick, details.numTicks) : parsedTick).toString()) }} /> )} - <span className='scalar-or-divider'>{marketCopy.or}</span> + <span className='scalar-or-divider'>{commonCopy.or}</span> <label className='scalar-invalid-toggle'> <input type='checkbox' disabled={disabled} checked={isInvalid} onChange={event => onInvalidChange(event.currentTarget.checked)} /> <span>{commonCopy.invalid}</span> @@ -98,16 +101,16 @@ export function ScalarOutcomePicker({ action, details, disabled = false, isInval </div> </div> <DataGrid className='scalar-slider-stats'> - {showMinMax ? <MetricField label={marketCopy.minValue}>{details.minValueLabel}</MetricField> : undefined} - <MetricField label={marketCopy.selectedTick}>{selectedTickLabel}</MetricField> - <MetricField label={showMinMax ? marketCopy.selectedOutcome : marketCopy.currentValue} valueTagName='span'> + {showMinMax ? <MetricField label={commonCopy.minValue}>{details.minValueLabel}</MetricField> : undefined} + <MetricField label={commonCopy.selectedTick}>{selectedTickLabel}</MetricField> + <MetricField label={showMinMax ? commonCopy.selectedOutcome : commonCopy.currentValue} valueTagName='span'> {isInvalid || resolvedScalarValueInput === undefined ? ( selectedOutcomeLabel ) : ( <span className='scalar-value-editor'> <span className='scalar-value-input-row'> <FormInput - aria-label={marketCopy.scalarValue} + aria-label={commonCopy.scalarValue} aria-describedby={scalarValueError === undefined ? undefined : scalarValueErrorId} disabled={disabled || isInvalid} inputMode='decimal' @@ -121,7 +124,7 @@ export function ScalarOutcomePicker({ action, details, disabled = false, isInval /> {details.answerUnit === undefined || details.answerUnit === '' ? undefined : <span className='scalar-value-unit'>{details.answerUnit}</span>} </span> - {scalarValueError === undefined ? <span className='field-help'>{marketCopy.scalarValueHelpText}</span> : undefined} + {scalarValueError === undefined ? <span className='field-help'>{commonCopy.scalarValueHelpText}</span> : undefined} {scalarValueError === undefined ? undefined : ( <span className='field-error' id={scalarValueErrorId}> {scalarValueError} @@ -130,7 +133,7 @@ export function ScalarOutcomePicker({ action, details, disabled = false, isInval </span> )} </MetricField> - {showMinMax ? <MetricField label={marketCopy.maxValue}>{details.maxValueLabel}</MetricField> : undefined} + {showMinMax ? <MetricField label={commonCopy.maxValue}>{details.maxValueLabel}</MetricField> : undefined} </DataGrid> {action === undefined ? undefined : <div className='actions'>{action}</div>} </div> diff --git a/ui/ts/components/SectionBlock.tsx b/ui/coreShared/ts/components/SectionBlock.tsx similarity index 100% rename from ui/ts/components/SectionBlock.tsx rename to ui/coreShared/ts/components/SectionBlock.tsx diff --git a/ui/ts/components/SimulationBanner.tsx b/ui/coreShared/ts/components/SimulationBanner.tsx similarity index 98% rename from ui/ts/components/SimulationBanner.tsx rename to ui/coreShared/ts/components/SimulationBanner.tsx index 2db1aa043..c3cf73ddb 100644 --- a/ui/ts/components/SimulationBanner.tsx +++ b/ui/coreShared/ts/components/SimulationBanner.tsx @@ -3,13 +3,14 @@ import * as simulationCopy from '../copy/simulation.js' import { useSignal } from '@preact/signals' import { useEffect, useLayoutEffect, useRef } from 'preact/hooks' import { getErrorMessage } from '../lib/errors.js' +import { SIMULATION_QUERY_PARAM, SIMULATION_QUERY_VALUE } from '../lib/activeEnvironment.js' import { buildRouteHref, getCurrentRouteHash, getRouteHashSearch } from '../lib/routing.js' import type { SimulationController } from '../simulation/controller.js' import { tryParseDecimalInput } from '../lib/decimal.js' import { formatCurrencyInputBalance, formatTimestampWithRelative } from '../lib/formatters.js' import { useCopyToClipboard } from '../hooks/useCopyToClipboard.js' import { getBrowserStorage } from '../lib/browserStorage.js' -import { getSimulationScenarioDescription, getSimulationScenarioLabel, SIMULATION_SCENARIOS } from '../simulation/scenarios.js' +import { getRegisteredSimulationScenarios, getSimulationScenarioDescription, getSimulationScenarioLabel } from '../simulation/scenarios.js' import { deleteSavedSimulationState, getSavedSimulationStateStorageSummary, persistSavedSimulationState, removeCorruptedSavedSimulationStates, type SavedSimulationStateRecord, type SavedSimulationStateStorageSummary } from '../simulation/savedStates.js' import { OperationModal } from './OperationModal.js' import { AddressValue } from './AddressValue.js' @@ -38,7 +39,7 @@ type NavigationOperation = 'cleanup' | 'delete' | 'import' | 'navigation' | 'sav function buildSimulationSearch(update: (params: URLSearchParams) => void) { const params = new URLSearchParams(getRouteHashSearch()) - params.set('simulate', '1') + params.set(SIMULATION_QUERY_PARAM, SIMULATION_QUERY_VALUE) update(params) const nextSearch = params.toString() return nextSearch === '' ? '' : `?${nextSearch}` @@ -405,7 +406,7 @@ export function SimulationBanner({ controller, onEnvironmentChanged = async () = }} > <optgroup label={simulationCopy.builtInScenarios}> - {SIMULATION_SCENARIOS.map(scenario => ( + {getRegisteredSimulationScenarios().map(scenario => ( <option key={scenario} value={`scenario:${scenario}`}> {getSimulationScenarioLabel(scenario)} </option> diff --git a/ui/ts/components/StateHint.tsx b/ui/coreShared/ts/components/StateHint.tsx similarity index 100% rename from ui/ts/components/StateHint.tsx rename to ui/coreShared/ts/components/StateHint.tsx diff --git a/ui/ts/components/StickyObjectContext.tsx b/ui/coreShared/ts/components/StickyObjectContext.tsx similarity index 100% rename from ui/ts/components/StickyObjectContext.tsx rename to ui/coreShared/ts/components/StickyObjectContext.tsx diff --git a/ui/coreShared/ts/components/TabNavigation.tsx b/ui/coreShared/ts/components/TabNavigation.tsx new file mode 100644 index 000000000..d7e435547 --- /dev/null +++ b/ui/coreShared/ts/components/TabNavigation.tsx @@ -0,0 +1,43 @@ +import * as appCopy from '../copy/app.js' +import { ViewTabs } from './ViewTabs.js' +import { buildRouteHref, getTopLevelRouteSearch } from '../lib/routing.js' +import type { RouteTabDefinition } from '../types/components.js' + +type TabNavigationProps = { + route: string + tabs: readonly RouteTabDefinition[] + onRouteChange: (route: string) => void +} + +export function TabNavigation({ route, tabs, onRouteChange }: TabNavigationProps) { + const options = tabs.map(tab => ({ + value: tab.route, + label: tab.label, + href: buildRouteHref(tab.hash, getTopLevelRouteSearch(tab.route)), + ...(tab.disabled ? { disabled: true } : {}), + ...(tab.disabled && tab.disabledReason !== undefined ? { reason: tab.disabledReason } : {}), + })) + const disabledReason = tabs.find(tab => tab.disabled === true)?.disabledReason + const fallbackRoute = tabs[0]?.route ?? route + const effectiveRoute = route === 'not-found' ? fallbackRoute : route + + return ( + <nav className='tab-nav' aria-label={appCopy.applicationSections} role='navigation'> + <ViewTabs ariaLabel={appCopy.applicationSections} semantics='navigation' value={effectiveRoute} variant='route' onChange={value => onRouteChange(value)} options={options} /> + <label className='mobile-route-select'> + <span>{appCopy.currentApplicationSection}</span> + <select aria-label={appCopy.currentApplicationSection} value={effectiveRoute} onChange={event => onRouteChange(event.currentTarget.value)}> + {options.map(option => ( + <option key={option.value} value={option.value} disabled={option.disabled}> + {option.label} + </option> + ))} + </select> + {disabledReason !== undefined ? <span className='detail disabled-reason'>{disabledReason}</span> : undefined} + </label> + <a className='protocol-guide-link' href={appCopy.protocolGuideHref} target='_blank' rel='noreferrer'> + {appCopy.protocolGuide} + </a> + </nav> + ) +} diff --git a/ui/ts/components/TimestampValue.tsx b/ui/coreShared/ts/components/TimestampValue.tsx similarity index 100% rename from ui/ts/components/TimestampValue.tsx rename to ui/coreShared/ts/components/TimestampValue.tsx diff --git a/ui/ts/components/TokenApprovalControl.tsx b/ui/coreShared/ts/components/TokenApprovalControl.tsx similarity index 100% rename from ui/ts/components/TokenApprovalControl.tsx rename to ui/coreShared/ts/components/TokenApprovalControl.tsx diff --git a/ui/ts/components/TransactionActionButton.tsx b/ui/coreShared/ts/components/TransactionActionButton.tsx similarity index 91% rename from ui/ts/components/TransactionActionButton.tsx rename to ui/coreShared/ts/components/TransactionActionButton.tsx index 6e6434bed..5369638b6 100644 --- a/ui/ts/components/TransactionActionButton.tsx +++ b/ui/coreShared/ts/components/TransactionActionButton.tsx @@ -28,7 +28,7 @@ export function TransactionActionButton({ ariaLabel, availability, className = ' } return ( <div className={`tx-action ${className}`.trim()}> - <button aria-label={ariaLabel} className={`tx-action-button ${tone}`} type={type} onClick={handleClick} disabled={isDisabled} title={disabledReason} aria-describedby={describedBy}> + <button aria-label={ariaLabel} aria-busy={pending} className={`tx-action-button ${tone}`} type={type} onClick={handleClick} disabled={isDisabled} title={disabledReason} aria-describedby={describedBy}> {pending ? <LoadingText announce={!isPendingGlobalTransactionPresentation(globalTransaction)}>{pendingLabel}</LoadingText> : idleLabel} </button> {shouldShowDisabledReason ? ( diff --git a/ui/ts/components/TransactionHashLink.tsx b/ui/coreShared/ts/components/TransactionHashLink.tsx similarity index 100% rename from ui/ts/components/TransactionHashLink.tsx rename to ui/coreShared/ts/components/TransactionHashLink.tsx diff --git a/ui/ts/components/TransactionNetworkValue.tsx b/ui/coreShared/ts/components/TransactionNetworkValue.tsx similarity index 100% rename from ui/ts/components/TransactionNetworkValue.tsx rename to ui/coreShared/ts/components/TransactionNetworkValue.tsx diff --git a/ui/ts/components/TransactionObjectContext.tsx b/ui/coreShared/ts/components/TransactionObjectContext.tsx similarity index 100% rename from ui/ts/components/TransactionObjectContext.tsx rename to ui/coreShared/ts/components/TransactionObjectContext.tsx diff --git a/ui/ts/components/TransactionPresentationNotice.tsx b/ui/coreShared/ts/components/TransactionPresentationNotice.tsx similarity index 100% rename from ui/ts/components/TransactionPresentationNotice.tsx rename to ui/coreShared/ts/components/TransactionPresentationNotice.tsx diff --git a/ui/ts/components/TransactionReview.tsx b/ui/coreShared/ts/components/TransactionReview.tsx similarity index 100% rename from ui/ts/components/TransactionReview.tsx rename to ui/coreShared/ts/components/TransactionReview.tsx diff --git a/ui/ts/components/TransactionStatusCard.tsx b/ui/coreShared/ts/components/TransactionStatusCard.tsx similarity index 100% rename from ui/ts/components/TransactionStatusCard.tsx rename to ui/coreShared/ts/components/TransactionStatusCard.tsx diff --git a/ui/ts/components/ViewTabs.tsx b/ui/coreShared/ts/components/ViewTabs.tsx similarity index 100% rename from ui/ts/components/ViewTabs.tsx rename to ui/coreShared/ts/components/ViewTabs.tsx diff --git a/ui/ts/components/WalletAssetControl.tsx b/ui/coreShared/ts/components/WalletAssetControl.tsx similarity index 100% rename from ui/ts/components/WalletAssetControl.tsx rename to ui/coreShared/ts/components/WalletAssetControl.tsx diff --git a/ui/ts/components/WarningSurface.tsx b/ui/coreShared/ts/components/WarningSurface.tsx similarity index 100% rename from ui/ts/components/WarningSurface.tsx rename to ui/coreShared/ts/components/WarningSurface.tsx diff --git a/ui/ts/components/WorkflowSubsection.tsx b/ui/coreShared/ts/components/WorkflowSubsection.tsx similarity index 100% rename from ui/ts/components/WorkflowSubsection.tsx rename to ui/coreShared/ts/components/WorkflowSubsection.tsx diff --git a/ui/ts/copy/README.md b/ui/coreShared/ts/copy/README.md similarity index 100% rename from ui/ts/copy/README.md rename to ui/coreShared/ts/copy/README.md diff --git a/ui/ts/copy/app.ts b/ui/coreShared/ts/copy/app.ts similarity index 97% rename from ui/ts/copy/app.ts rename to ui/coreShared/ts/copy/app.ts index 6481c866a..fc3fec37a 100644 --- a/ui/ts/copy/app.ts +++ b/ui/coreShared/ts/copy/app.ts @@ -1,4 +1,3 @@ -export const appDocumentTitleSuffix = 'Zoltar + Augur Statoblast' export const connecting = 'Connecting…' export const changeWallet = 'Change wallet' export const disconnectWallet = 'Disconnect' @@ -53,7 +52,6 @@ export const simulationBootstrapFailed = 'Simulation bootstrap failed' export const staleReadBackendDetail = 'Displayed onchain state may be behind the latest chain state. Refresh or switch RPC before acting on balances, settlement, or liquidation.' export const urlProvidedReadRpc = 'URL-provided read RPC' export const address = 'Address' -export const augurStatoblastTitle = 'Augur Statoblast' export const formatShowEarlierNavigationItems = (navigationLabel: string) => `Show earlier ${navigationLabel}` export const formatShowLaterNavigationItems = (navigationLabel: string) => `Show later ${navigationLabel}` export const connected = 'Connected' diff --git a/ui/ts/copy/common.ts b/ui/coreShared/ts/copy/common.ts similarity index 89% rename from ui/ts/copy/common.ts rename to ui/coreShared/ts/copy/common.ts index 7f15557ff..e6081db28 100644 --- a/ui/ts/copy/common.ts +++ b/ui/coreShared/ts/copy/common.ts @@ -140,3 +140,22 @@ export const formatAddTokenToWallet = (tokenLabel: CopyTemplateValue) => `Add ${ export const formatOpeningWalletForToken = (tokenLabel: CopyTemplateValue) => `Opening wallet to add ${tokenLabel}` export const formatRetryWalletAssetRequest = (tokenLabel: CopyTemplateValue) => `Retry adding ${tokenLabel} to wallet` export const formatWalletAssetRequestAccepted = (tokenLabel: CopyTemplateValue) => `${tokenLabel} wallet request accepted` +export const answerUnit = 'Answer Unit' +export const created = 'Created' +export const currentValue = 'Current Value' +export const displayRange = 'Display Range' +export const endTime = 'End Time' +export const maxValue = 'Max Value' +export const minValue = 'Min Value' +export const or = 'or' +export const outcomes = 'Outcomes' +export const questionDetailsLoadingLabel = 'Loading question details…' +export const questionTimeline = 'Question timeline' +export const questionType = 'Question Type' +export const selectedOutcome = 'Selected Outcome' +export const selectedTick = 'Selected Tick' +export const scalarValue = 'Scalar Value' +export const scalarValueHelpText = 'Enter a value on an increment.' +export const scalarValueInvalid = 'Enter a value between the minimum and maximum that falls on an increment.' +export const ticks = 'Ticks' +export const untitledQuestion = 'Untitled question' diff --git a/ui/coreShared/ts/copy/marketType.ts b/ui/coreShared/ts/copy/marketType.ts new file mode 100644 index 000000000..5ddd129be --- /dev/null +++ b/ui/coreShared/ts/copy/marketType.ts @@ -0,0 +1,3 @@ +export const binary = 'Binary' +export const categorical = 'Categorical' +export const scalar = 'Scalar' diff --git a/ui/ts/copy/pricing.ts b/ui/coreShared/ts/copy/pricing.ts similarity index 100% rename from ui/ts/copy/pricing.ts rename to ui/coreShared/ts/copy/pricing.ts diff --git a/ui/ts/copy/simulation.ts b/ui/coreShared/ts/copy/simulation.ts similarity index 100% rename from ui/ts/copy/simulation.ts rename to ui/coreShared/ts/copy/simulation.ts diff --git a/ui/ts/copy/transaction.ts b/ui/coreShared/ts/copy/transaction.ts similarity index 100% rename from ui/ts/copy/transaction.ts rename to ui/coreShared/ts/copy/transaction.ts diff --git a/ui/ts/copy/transactionReview.ts b/ui/coreShared/ts/copy/transactionReview.ts similarity index 100% rename from ui/ts/copy/transactionReview.ts rename to ui/coreShared/ts/copy/transactionReview.ts diff --git a/ui/ts/copy/types.ts b/ui/coreShared/ts/copy/types.ts similarity index 100% rename from ui/ts/copy/types.ts rename to ui/coreShared/ts/copy/types.ts diff --git a/ui/ts/copy/userMessages.ts b/ui/coreShared/ts/copy/userMessages.ts similarity index 100% rename from ui/ts/copy/userMessages.ts rename to ui/coreShared/ts/copy/userMessages.ts diff --git a/ui/ts/hooks/useCopyToClipboard.ts b/ui/coreShared/ts/hooks/useCopyToClipboard.ts similarity index 100% rename from ui/ts/hooks/useCopyToClipboard.ts rename to ui/coreShared/ts/hooks/useCopyToClipboard.ts diff --git a/ui/ts/hooks/useErc20Loader.ts b/ui/coreShared/ts/hooks/useErc20Loader.ts similarity index 86% rename from ui/ts/hooks/useErc20Loader.ts rename to ui/coreShared/ts/hooks/useErc20Loader.ts index 4cf303258..b113070a4 100644 --- a/ui/ts/hooks/useErc20Loader.ts +++ b/ui/coreShared/ts/hooks/useErc20Loader.ts @@ -1,5 +1,4 @@ import { useSignal } from '@preact/signals' -import { loadErc20Allowance, loadErc20Balance } from '../protocol/index.js' import { createConnectedReadClient } from '../lib/clients.js' import { getErrorMessage, isRecoverableContractReadError } from '../lib/errors.js' import { useRequestGuard } from '../lib/requestGuard.js' @@ -31,11 +30,11 @@ function useErc20Loader<TArgs extends unknown[]>(loadFn: (client: ReadClient, .. return { invalidate, signal, reload } } -export function useErc20BalanceLoader() { +export function useErc20BalanceLoader(loadErc20Balance: (client: ReadClient, tokenAddress: `0x${string}`, accountAddress: `0x${string}`) => Promise<bigint>) { return useErc20Loader(loadErc20Balance) } -export function useErc20AllowanceLoader() { +export function useErc20AllowanceLoader(loadErc20Allowance: (client: ReadClient, tokenAddress: `0x${string}`, ownerAddress: `0x${string}`, spenderAddress: `0x${string}`) => Promise<bigint>) { const signal = useSignal<TokenApprovalState>({ error: undefined, loading: false, diff --git a/ui/ts/hooks/useFormState.ts b/ui/coreShared/ts/hooks/useFormState.ts similarity index 100% rename from ui/ts/hooks/useFormState.ts rename to ui/coreShared/ts/hooks/useFormState.ts diff --git a/ui/ts/hooks/useLoadController.ts b/ui/coreShared/ts/hooks/useLoadController.ts similarity index 100% rename from ui/ts/hooks/useLoadController.ts rename to ui/coreShared/ts/hooks/useLoadController.ts diff --git a/ui/ts/hooks/useModalFocusIsolation.ts b/ui/coreShared/ts/hooks/useModalFocusIsolation.ts similarity index 100% rename from ui/ts/hooks/useModalFocusIsolation.ts rename to ui/coreShared/ts/hooks/useModalFocusIsolation.ts diff --git a/ui/ts/injectedEthereum.ts b/ui/coreShared/ts/injectedEthereum.ts similarity index 100% rename from ui/ts/injectedEthereum.ts rename to ui/coreShared/ts/injectedEthereum.ts diff --git a/ui/ts/lib/actionAvailability.ts b/ui/coreShared/ts/lib/actionAvailability.ts similarity index 100% rename from ui/ts/lib/actionAvailability.ts rename to ui/coreShared/ts/lib/actionAvailability.ts diff --git a/ui/ts/lib/actionFeedback.ts b/ui/coreShared/ts/lib/actionFeedback.ts similarity index 100% rename from ui/ts/lib/actionFeedback.ts rename to ui/coreShared/ts/lib/actionFeedback.ts diff --git a/ui/ts/lib/actionGuards.ts b/ui/coreShared/ts/lib/actionGuards.ts similarity index 100% rename from ui/ts/lib/actionGuards.ts rename to ui/coreShared/ts/lib/actionGuards.ts diff --git a/ui/ts/lib/activeEnvironment.ts b/ui/coreShared/ts/lib/activeEnvironment.ts similarity index 89% rename from ui/ts/lib/activeEnvironment.ts rename to ui/coreShared/ts/lib/activeEnvironment.ts index fd329cc3c..1da791519 100644 --- a/ui/ts/lib/activeEnvironment.ts +++ b/ui/coreShared/ts/lib/activeEnvironment.ts @@ -5,7 +5,7 @@ import { getPublicNetworkProfile, getPublicNetworkProfileForChainId, MAINNET_NET import type { SimulationController } from '../simulation/controller.js' import { getSavedSimulationStateEnvelope } from '../simulation/savedStates.js' import { createSimulationBackend } from '../simulation/tevmBackend.js' -import { normalizeSimulationScenario, type SimulationScenario } from '../simulation/scenarios.js' +import { getRegisteredSimulationScenarios, type SimulationScenario } from '../simulation/scenarios.js' type LocationLike = { hash?: string @@ -19,16 +19,17 @@ let activeBackend: ChainBackend | undefined = undefined let activeSimulationController: SimulationController | undefined = undefined let initializeActiveEnvironmentGeneration = 0 -const SIMULATION_QUERY_PARAM = 'simulate' -const SIMULATION_QUERY_VALUE = '1' +export const SIMULATION_QUERY_PARAM = 'simulate' +export const SIMULATION_QUERY_VALUE = '1' const NETWORK_QUERY_PARAM = 'network' type InitializeActiveEnvironmentDependencies = { + appId?: 'zoltar' | 'statoblast' | 'trading' createInjectedBackend?: typeof createInjectedBackend - createSimulationBackend: typeof createSimulationBackend + createSimulationBackend?: typeof createSimulationBackend } -const defaultInitializeActiveEnvironmentDependencies: InitializeActiveEnvironmentDependencies = { +const defaultInitializeActiveEnvironmentDependencies = { createInjectedBackend, createSimulationBackend, } @@ -59,8 +60,9 @@ export function shouldFollowWalletNetwork(location: LocationLike = window.locati } function getSimulationScenario(location: LocationLike): SimulationScenario { - const params = readLocationParams(location) - return normalizeSimulationScenario(params.get('simScenario') ?? undefined) + const raw = readLocationParams(location).get('simScenario') ?? undefined + if (raw === undefined) return 'baseline' + return getRegisteredSimulationScenarios().includes(raw) ? raw : 'baseline' } function getSimulationStateId(location: LocationLike) { @@ -119,13 +121,17 @@ export async function initializeActiveEnvironment(location: LocationLike = windo initialBootstrapError = `Saved simulation state "${savedStateId}" could not be loaded. Falling back to the baseline scenario.` } } + const createSimulationBackendImpl = dependencies.createSimulationBackend ?? createSimulationBackend + const simulationAppId = dependencies.appId ?? 'zoltar' const simulationBackend = savedStateId !== undefined && savedState !== undefined - ? await dependencies.createSimulationBackend({ + ? await createSimulationBackendImpl({ + appId: simulationAppId, savedState, savedStateId, }) - : await dependencies.createSimulationBackend({ + : await createSimulationBackendImpl({ + appId: simulationAppId, ...(initialBootstrapError === undefined ? {} : { initialBootstrapError }), scenario: savedStateId === undefined ? getSimulationScenario(location) : 'baseline', }) diff --git a/ui/ts/lib/address.ts b/ui/coreShared/ts/lib/address.ts similarity index 100% rename from ui/ts/lib/address.ts rename to ui/coreShared/ts/lib/address.ts diff --git a/ui/ts/lib/assert.ts b/ui/coreShared/ts/lib/assert.ts similarity index 100% rename from ui/ts/lib/assert.ts rename to ui/coreShared/ts/lib/assert.ts diff --git a/ui/ts/lib/assertActiveWallet.ts b/ui/coreShared/ts/lib/assertActiveWallet.ts similarity index 100% rename from ui/ts/lib/assertActiveWallet.ts rename to ui/coreShared/ts/lib/assertActiveWallet.ts diff --git a/ui/ts/lib/browserStorage.ts b/ui/coreShared/ts/lib/browserStorage.ts similarity index 100% rename from ui/ts/lib/browserStorage.ts rename to ui/coreShared/ts/lib/browserStorage.ts diff --git a/ui/ts/lib/caseInsensitive.ts b/ui/coreShared/ts/lib/caseInsensitive.ts similarity index 100% rename from ui/ts/lib/caseInsensitive.ts rename to ui/coreShared/ts/lib/caseInsensitive.ts diff --git a/ui/ts/lib/chainBackend.ts b/ui/coreShared/ts/lib/chainBackend.ts similarity index 100% rename from ui/ts/lib/chainBackend.ts rename to ui/coreShared/ts/lib/chainBackend.ts diff --git a/ui/ts/lib/chainId.ts b/ui/coreShared/ts/lib/chainId.ts similarity index 100% rename from ui/ts/lib/chainId.ts rename to ui/coreShared/ts/lib/chainId.ts diff --git a/ui/ts/lib/chainTimestamp.ts b/ui/coreShared/ts/lib/chainTimestamp.ts similarity index 100% rename from ui/ts/lib/chainTimestamp.ts rename to ui/coreShared/ts/lib/chainTimestamp.ts diff --git a/ui/ts/lib/clients.ts b/ui/coreShared/ts/lib/clients.ts similarity index 100% rename from ui/ts/lib/clients.ts rename to ui/coreShared/ts/lib/clients.ts diff --git a/ui/ts/lib/decimal.ts b/ui/coreShared/ts/lib/decimal.ts similarity index 100% rename from ui/ts/lib/decimal.ts rename to ui/coreShared/ts/lib/decimal.ts diff --git a/ui/coreShared/ts/lib/deploymentStatus.ts b/ui/coreShared/ts/lib/deploymentStatus.ts new file mode 100644 index 000000000..1a7cf74d3 --- /dev/null +++ b/ui/coreShared/ts/lib/deploymentStatus.ts @@ -0,0 +1,5 @@ +import type { DeploymentStatus } from '../types/contracts.js' + +export function hasDeployedStep(steps: DeploymentStatus[], stepId: DeploymentStatus['id']) { + return steps.some(step => step.id === stepId && step.deployed) +} diff --git a/ui/ts/lib/errors.ts b/ui/coreShared/ts/lib/errors.ts similarity index 100% rename from ui/ts/lib/errors.ts rename to ui/coreShared/ts/lib/errors.ts diff --git a/ui/coreShared/ts/lib/formInputs.ts b/ui/coreShared/ts/lib/formInputs.ts new file mode 100644 index 000000000..b0fd449d1 --- /dev/null +++ b/ui/coreShared/ts/lib/formInputs.ts @@ -0,0 +1,62 @@ +import { parseDecimalInput, tryParseDecimalInput } from './decimal.js' + +export function parseRepAmountInput(value: string, label: string) { + return parseDecimalInput(value, label, 18) +} + +export function parseEthAmountInput(value: string, label: string) { + return parseDecimalInput(value, label, 18) +} + +export function tryParseRepAmountInput(value: string) { + return tryParseDecimalInput(value, 18) +} + +export function tryParseEthAmountInput(value: string) { + return tryParseDecimalInput(value, 18) +} + +export function parseOptionalRepAmountInput(value: string) { + const trimmed = value.trim() + if (trimmed === '') return undefined + return tryParseRepAmountInput(trimmed) +} + +export function parseTradingAmountInput(value: string, label: string) { + return parseDecimalInput(value, label, 18) +} + +export function tryParseTradingAmountInput(value: string) { + return tryParseDecimalInput(value, 18) +} + +export function parseTruthAuctionPriceInput(value: string, label: string) { + return parseDecimalInput(value, label, 18) +} + +export function tryParseTruthAuctionPriceInput(value: string) { + return tryParseDecimalInput(value, 18) +} + +export function parseTruthAuctionAmountInput(value: string, label: string) { + return parseDecimalInput(value, label, 18) +} + +export function tryParseTruthAuctionAmountInput(value: string) { + return tryParseDecimalInput(value, 18) +} + +export function tryParseTimestampInput(value: string) { + const trimmed = value.trim() + if (/^-?\d+$/.test(trimmed)) return BigInt(trimmed) + const timestampMs = new Date(value).getTime() + if (Number.isNaN(timestampMs)) return undefined + return BigInt(Math.floor(timestampMs / 1000)) +} + +export function parseTimestampInput(value: string, label: string) { + const timestamp = tryParseTimestampInput(value) + if (timestamp === undefined) throw new Error(`${label} is invalid`) + if (timestamp < 0n) throw new Error(`${label} must not be before the Unix epoch`) + return timestamp +} diff --git a/ui/ts/lib/formatters.ts b/ui/coreShared/ts/lib/formatters.ts similarity index 100% rename from ui/ts/lib/formatters.ts rename to ui/coreShared/ts/lib/formatters.ts diff --git a/ui/ts/lib/inputs.ts b/ui/coreShared/ts/lib/inputs.ts similarity index 100% rename from ui/ts/lib/inputs.ts rename to ui/coreShared/ts/lib/inputs.ts diff --git a/ui/ts/lib/integerInput.ts b/ui/coreShared/ts/lib/integerInput.ts similarity index 100% rename from ui/ts/lib/integerInput.ts rename to ui/coreShared/ts/lib/integerInput.ts diff --git a/ui/ts/lib/loadState.ts b/ui/coreShared/ts/lib/loadState.ts similarity index 100% rename from ui/ts/lib/loadState.ts rename to ui/coreShared/ts/lib/loadState.ts diff --git a/ui/ts/features/markets/lib/marketType.ts b/ui/coreShared/ts/lib/marketType.ts similarity index 62% rename from ui/ts/features/markets/lib/marketType.ts rename to ui/coreShared/ts/lib/marketType.ts index e83b8d5e0..dda5753d4 100644 --- a/ui/ts/features/markets/lib/marketType.ts +++ b/ui/coreShared/ts/lib/marketType.ts @@ -1,6 +1,6 @@ -import * as marketCopy from '../../../copy/market.js' -import { assertNever } from '../../../lib/assert.js' -import type { MarketType } from '../../../types/contracts.js' +import { assertNever } from './assert.js' +import * as marketCopy from '../copy/marketType.js' +import type { MarketType } from '../types/contracts.js' export function getMarketTypeLabel(marketType: MarketType) { switch (marketType) { diff --git a/ui/ts/lib/network.ts b/ui/coreShared/ts/lib/network.ts similarity index 100% rename from ui/ts/lib/network.ts rename to ui/coreShared/ts/lib/network.ts diff --git a/ui/ts/lib/networkProfile.ts b/ui/coreShared/ts/lib/networkProfile.ts similarity index 95% rename from ui/ts/lib/networkProfile.ts rename to ui/coreShared/ts/lib/networkProfile.ts index 4d822280e..7fb78ba90 100644 --- a/ui/ts/lib/networkProfile.ts +++ b/ui/coreShared/ts/lib/networkProfile.ts @@ -112,18 +112,20 @@ export function getNetworkSwitchTarget(profile: NetworkProfile) { return profile.id === 'mainnet' ? 'Ethereum mainnet' : profile.displayName } -let runtimeNetworkProfile: NetworkProfile = MAINNET_NETWORK_PROFILE +declare global { + var __zoltarRuntimeNetworkProfile__: NetworkProfile | undefined +} export function getRuntimeNetworkProfile() { - return runtimeNetworkProfile + return globalThis.__zoltarRuntimeNetworkProfile__ ?? MAINNET_NETWORK_PROFILE } export function setRuntimeNetworkProfile(profile: NetworkProfile) { - runtimeNetworkProfile = profile + globalThis.__zoltarRuntimeNetworkProfile__ = profile } export function resetRuntimeNetworkProfile() { - runtimeNetworkProfile = MAINNET_NETWORK_PROFILE + globalThis.__zoltarRuntimeNetworkProfile__ = undefined } export function createSimulationProfile({ genesisRepTokenAddress, wethAddress }: { genesisRepTokenAddress: Address; wethAddress: Address }): NetworkProfile { diff --git a/ui/ts/lib/noticeStack.ts b/ui/coreShared/ts/lib/noticeStack.ts similarity index 100% rename from ui/ts/lib/noticeStack.ts rename to ui/coreShared/ts/lib/noticeStack.ts diff --git a/ui/ts/features/markets/lib/outcomeLabels.ts b/ui/coreShared/ts/lib/outcomeLabels.ts similarity index 79% rename from ui/ts/features/markets/lib/outcomeLabels.ts rename to ui/coreShared/ts/lib/outcomeLabels.ts index 3f67578b7..199a373bf 100644 --- a/ui/ts/features/markets/lib/outcomeLabels.ts +++ b/ui/coreShared/ts/lib/outcomeLabels.ts @@ -1,4 +1,4 @@ -import { sameCaseInsensitiveText } from '../../../lib/caseInsensitive.js' +import { sameCaseInsensitiveText } from '../lib/caseInsensitive.js' export function isInvalidOutcomeLabel(outcome: string) { return sameCaseInsensitiveText(outcome, 'invalid') diff --git a/ui/ts/lib/pagination.ts b/ui/coreShared/ts/lib/pagination.ts similarity index 100% rename from ui/ts/lib/pagination.ts rename to ui/coreShared/ts/lib/pagination.ts diff --git a/ui/ts/lib/questionId.ts b/ui/coreShared/ts/lib/questionId.ts similarity index 100% rename from ui/ts/lib/questionId.ts rename to ui/coreShared/ts/lib/questionId.ts diff --git a/ui/ts/lib/refreshState.ts b/ui/coreShared/ts/lib/refreshState.ts similarity index 100% rename from ui/ts/lib/refreshState.ts rename to ui/coreShared/ts/lib/refreshState.ts diff --git a/ui/ts/lib/requestGuard.ts b/ui/coreShared/ts/lib/requestGuard.ts similarity index 100% rename from ui/ts/lib/requestGuard.ts rename to ui/coreShared/ts/lib/requestGuard.ts diff --git a/ui/ts/lib/requireWalletConnection.ts b/ui/coreShared/ts/lib/requireWalletConnection.ts similarity index 100% rename from ui/ts/lib/requireWalletConnection.ts rename to ui/coreShared/ts/lib/requireWalletConnection.ts diff --git a/ui/ts/lib/required.ts b/ui/coreShared/ts/lib/required.ts similarity index 100% rename from ui/ts/lib/required.ts rename to ui/coreShared/ts/lib/required.ts diff --git a/ui/coreShared/ts/lib/routing.ts b/ui/coreShared/ts/lib/routing.ts new file mode 100644 index 000000000..b569a4ace --- /dev/null +++ b/ui/coreShared/ts/lib/routing.ts @@ -0,0 +1,116 @@ +export type AppRoute = string + +export type RouteDefinition = { + readonly hash: string + readonly name: AppRoute + readonly queryParameters?: ReadonlySet<string> +} + +export type RoutingConfig = { + readonly defaultRoute: AppRoute + readonly routes: readonly RouteDefinition[] +} + +const SHARED_ROUTE_QUERY_PARAMETERS = new Set(['network', 'rpcUrl', 'simScenario', 'simState', 'simulate', 'universe']) + +type RoutingState = { + readonly config: RoutingConfig + readonly routeByHash: Readonly<Record<string, AppRoute>> + readonly hashByRoute: Readonly<Record<string, string>> + readonly queryParametersByRoute: Readonly<Record<string, ReadonlySet<string>>> +} + +function buildRoutingState(config: RoutingConfig): RoutingState { + const routeByHash: Record<string, AppRoute> = {} + const hashByRoute: Record<string, string> = {} + const queryParametersByRoute: Record<string, ReadonlySet<string>> = {} + for (const route of config.routes) { + routeByHash[route.hash] = route.name + hashByRoute[route.name] = route.hash + queryParametersByRoute[route.name] = route.queryParameters ?? new Set() + } + return { config, routeByHash, hashByRoute, queryParametersByRoute } +} + +declare global { + var __zoltarActiveRoutingState__: RoutingState | undefined +} + +function getRoutingState(): RoutingState | undefined { + return globalThis.__zoltarActiveRoutingState__ +} + +export function installRouting(config: RoutingConfig) { + globalThis.__zoltarActiveRoutingState__ = buildRoutingState(config) +} + +export function resetRoutingForTesting() { + globalThis.__zoltarActiveRoutingState__ = undefined +} + +function requireRouting(): RoutingState { + const activeRouting = getRoutingState() + if (activeRouting === undefined) throw new Error('Routing has not been installed. Call installRouting() during application bootstrap before reading routes.') + return activeRouting +} + +export function getRouteHashForName(route: AppRoute) { + const hash = requireRouting().hashByRoute[route] + if (hash === undefined) throw new Error(`Unknown route: ${route}`) + return hash +} + +function splitRouteHash(hash: string) { + const queryIndex = hash.indexOf('?') + if (queryIndex === -1) + return { + routeHash: hash, + search: '', + } + + return { + routeHash: hash.slice(0, queryIndex), + search: hash.slice(queryIndex), + } +} + +export function ensureRouteHash() { + const routing = requireRouting() + if (window.location.hash === '') window.location.hash = routing.hashByRoute[routing.config.defaultRoute] ?? '' +} + +export function getCurrentRoute(): AppRoute | 'not-found' { + const routing = requireRouting() + const { routeHash } = splitRouteHash(window.location.hash) + return routing.routeByHash[routeHash] ?? (routeHash === '' ? routing.config.defaultRoute : 'not-found') +} + +export function getRouteHash(route: AppRoute) { + return getRouteHashForName(route) +} + +export function getRouteHashSearch(hash = window.location.hash) { + return splitRouteHash(hash).search +} + +export function getTopLevelRouteSearch(nextRoute: AppRoute, search = getRouteHashSearch(), preservedParameters: ReadonlySet<string> = new Set()) { + const routing = requireRouting() + const destinationParameters = routing.queryParametersByRoute[nextRoute] ?? new Set<string>() + const sourceParameters = new URLSearchParams(search) + const destinationSearch = new URLSearchParams() + for (const [key, value] of sourceParameters) { + if (SHARED_ROUTE_QUERY_PARAMETERS.has(key) || destinationParameters.has(key) || preservedParameters.has(key)) destinationSearch.append(key, value) + } + const serializedSearch = destinationSearch.toString() + return serializedSearch === '' ? '' : `?${serializedSearch}` +} + +export function getCurrentRouteHash() { + const routing = requireRouting() + const { routeHash } = splitRouteHash(window.location.hash) + return routeHash === '' ? (routing.hashByRoute[routing.config.defaultRoute] ?? '') : routeHash +} + +export function buildRouteHref(routeHash: string, search: string) { + return `${routeHash}${search}` +} diff --git a/ui/ts/lib/rpcConfig.ts b/ui/coreShared/ts/lib/rpcConfig.ts similarity index 100% rename from ui/ts/lib/rpcConfig.ts rename to ui/coreShared/ts/lib/rpcConfig.ts diff --git a/ui/ts/features/markets/lib/scalarOutcome.ts b/ui/coreShared/ts/lib/scalarOutcome.ts similarity index 96% rename from ui/ts/features/markets/lib/scalarOutcome.ts rename to ui/coreShared/ts/lib/scalarOutcome.ts index fe9d5be22..36faf9a7f 100644 --- a/ui/ts/features/markets/lib/scalarOutcome.ts +++ b/ui/coreShared/ts/lib/scalarOutcome.ts @@ -1,5 +1,5 @@ -import { parseDecimalInput } from '../../../lib/decimal.js' -import { getVisualRatio } from '../../../lib/visualMetrics.js' +import { parseDecimalInput } from './decimal.js' +import { getVisualRatio } from './visualMetrics.js' import { formatScalarOutcomeIndexLabel as formatSharedScalarOutcomeIndexLabel, formatScalarOutcomeLabel as formatSharedScalarOutcomeLabel, MAX_PRECISE_SCALAR_TICK_COUNT, type ScalarQuestionDetails } from '@zoltar/shared/scalarOutcome' export { diff --git a/ui/ts/lib/sepoliaDeploymentConfig.ts b/ui/coreShared/ts/lib/sepoliaDeploymentConfig.ts similarity index 100% rename from ui/ts/lib/sepoliaDeploymentConfig.ts rename to ui/coreShared/ts/lib/sepoliaDeploymentConfig.ts diff --git a/ui/ts/lib/time.ts b/ui/coreShared/ts/lib/time.ts similarity index 100% rename from ui/ts/lib/time.ts rename to ui/coreShared/ts/lib/time.ts diff --git a/ui/ts/lib/tokenApproval.ts b/ui/coreShared/ts/lib/tokenApproval.ts similarity index 100% rename from ui/ts/lib/tokenApproval.ts rename to ui/coreShared/ts/lib/tokenApproval.ts diff --git a/ui/ts/lib/transactionPresentations.tsx b/ui/coreShared/ts/lib/transactionPresentations.tsx similarity index 87% rename from ui/ts/lib/transactionPresentations.tsx rename to ui/coreShared/ts/lib/transactionPresentations.tsx index 9f5bbdf09..6f02073c0 100644 --- a/ui/ts/lib/transactionPresentations.tsx +++ b/ui/coreShared/ts/lib/transactionPresentations.tsx @@ -5,6 +5,26 @@ import type { Hash } from '@zoltar/shared/ethereum' import { formatCurrencyBalanceWithUnit } from './formatters.js' import type { TransactionRequestPreview } from './chainBackend.js' import type { GlobalTransactionPresentation, GlobalTransactionRow, TransactionIntent } from '../types/components.js' +import { AddressValue } from '../components/AddressValue.js' + +export type PoolUniverseTransactionContext = { + securityPoolAddress?: string | undefined + universeId?: bigint | undefined +} + +export function humanizeTransactionAction(action: string) { + return action + .replace(/([A-Z])/g, ' $1') + .replace(/^./, value => value.toUpperCase()) + .replaceAll(/\bRep\b/g, commonCopy.rep) + .replaceAll(/\bEth\b/g, commonCopy.eth) + .replaceAll(/\bWeth\b/g, commonCopy.weth) +} + +export function getPoolUniverseTransactionRows(context: PoolUniverseTransactionContext | undefined): GlobalTransactionRow[] | undefined { + if (context === undefined) return undefined + return context.securityPoolAddress === undefined || context.securityPoolAddress.trim() === '' ? [] : [{ identityKey: 'security-pool', label: transactionCopy.pool, value: <AddressValue address={context.securityPoolAddress} /> }] +} export function buildPresentation({ detail, diff --git a/ui/ts/lib/transactionTray.ts b/ui/coreShared/ts/lib/transactionTray.ts similarity index 100% rename from ui/ts/lib/transactionTray.ts rename to ui/coreShared/ts/lib/transactionTray.ts diff --git a/ui/ts/lib/urlParams.ts b/ui/coreShared/ts/lib/urlParams.ts similarity index 100% rename from ui/ts/lib/urlParams.ts rename to ui/coreShared/ts/lib/urlParams.ts diff --git a/ui/ts/lib/userCopy.ts b/ui/coreShared/ts/lib/userCopy.ts similarity index 100% rename from ui/ts/lib/userCopy.ts rename to ui/coreShared/ts/lib/userCopy.ts diff --git a/ui/ts/lib/viewState.ts b/ui/coreShared/ts/lib/viewState.ts similarity index 100% rename from ui/ts/lib/viewState.ts rename to ui/coreShared/ts/lib/viewState.ts diff --git a/ui/ts/lib/visualMetrics.ts b/ui/coreShared/ts/lib/visualMetrics.ts similarity index 100% rename from ui/ts/lib/visualMetrics.ts rename to ui/coreShared/ts/lib/visualMetrics.ts diff --git a/ui/ts/lib/walletAsset.ts b/ui/coreShared/ts/lib/walletAsset.ts similarity index 100% rename from ui/ts/lib/walletAsset.ts rename to ui/coreShared/ts/lib/walletAsset.ts diff --git a/ui/ts/lib/writeAction.ts b/ui/coreShared/ts/lib/writeAction.ts similarity index 100% rename from ui/ts/lib/writeAction.ts rename to ui/coreShared/ts/lib/writeAction.ts diff --git a/ui/ts/protocol/securityPoolActions.ts b/ui/coreShared/ts/protocol/securityPoolActions.ts similarity index 100% rename from ui/ts/protocol/securityPoolActions.ts rename to ui/coreShared/ts/protocol/securityPoolActions.ts diff --git a/ui/ts/protocol/truthAuctionMath.ts b/ui/coreShared/ts/protocol/truthAuctionMath.ts similarity index 100% rename from ui/ts/protocol/truthAuctionMath.ts rename to ui/coreShared/ts/protocol/truthAuctionMath.ts diff --git a/ui/coreShared/ts/simulation/bootstrap.ts b/ui/coreShared/ts/simulation/bootstrap.ts new file mode 100644 index 000000000..e3555ec84 --- /dev/null +++ b/ui/coreShared/ts/simulation/bootstrap.ts @@ -0,0 +1,453 @@ +import { createMemoryClient } from 'tevm' +import { encodeAbiParameters, encodeDeployData, getCreateAddress, keccak256, toHex, type Address, type Hex } from '@zoltar/shared/ethereum' +import { ReputationToken_ReputationToken, Zoltar_Zoltar, statoblast_WETH9_WETH9 } from '../contractArtifact.js' +import type { ReadClient, WriteClient } from '../lib/chainBackend.js' +import type { DeploymentStep } from '../types/contracts.js' +import { MAINNET_WETH_ADDRESS, setRuntimeNetworkProfile, type NetworkProfile } from '../lib/networkProfile.js' +import { initializeSimulationClock } from './clock.js' +import type { SimulationScenario } from './scenarios.js' + +export type TevmLikeClient = ReturnType<typeof createMemoryClient> + +export type BootstrapProgressHandler = (progress: { label: string; value: number }) => Promise<void> | void + +const ETH_BALANCE_AMOUNT = 10n ** 30n +const GENESIS_UNIVERSE_ID = 0n +const REP_TOKEN_MINT_AMOUNT = 3_000_000n * 10n ** 18n +const WETH_TOKEN_MINT_AMOUNT = 10_000n * 10n ** 18n +const WETH_NAME_SLOT = 0n +const WETH_SYMBOL_SLOT = 1n +const WETH_DECIMALS_SLOT = 2n +const ZOLTAR_GENESIS_REPUTATION_TOKEN_OFFSET = 3n +const ZOLTAR_UNIVERSE_THEORETICAL_SUPPLIES_SLOT = 2n +const ZOLTAR_UNIVERSES_SLOT = 0n + +async function yieldToBrowser() { + await new Promise<void>(resolve => { + setTimeout(resolve, 0) + }) +} + +async function withTimeout<TResult>(work: Promise<TResult>, timeoutMilliseconds: number, message: string) { + let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined + try { + return await Promise.race([ + work, + new Promise<TResult>((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error(message)) + }, timeoutMilliseconds) + }), + ]) + } finally { + if (timeoutId !== undefined) clearTimeout(timeoutId) + } +} + +function clampProgress(value: number) { + return Math.max(0, Math.min(1, value)) +} + +export async function reportBootstrapProgress(onProgress: BootstrapProgressHandler | undefined, label: string, value: number) { + await onProgress?.({ + label, + value: clampProgress(value), + }) + await yieldToBrowser() +} + +function storageIndex(slot: bigint) { + return toHex(slot, { size: 32 }) +} + +function storageValue(value: bigint) { + return toHex(value, { size: 32 }) +} + +function shortStringStorageValue(value: string) { + const valueHex = toHex(value).slice(2) + const byteLength = valueHex.length / 2 + if (byteLength > 31) throw new Error('Simulation token metadata exceeds Solidity short-string storage') + return storageValue(BigInt(`0x${valueHex.padEnd(62, '0')}${(byteLength * 2).toString(16).padStart(2, '0')}`)) +} + +function requireReceiptContractAddress(code: Hex | undefined, address: Address, label: string) { + if (code === undefined || code === '0x') throw new Error(`Failed to deploy ${label} at ${address}`) +} + +async function deployContract(writeClient: WriteClient, address: Address, label: string, data: Hex) { + const hash = await writeClient.sendTransaction({ data }) + await writeClient.waitForTransactionReceipt({ hash }) + const code = await writeClient.getCode({ address }) + requireReceiptContractAddress(code, address, label) +} + +function getZoltarUniverseBaseSlot(universeId: bigint) { + return BigInt(keccak256(encodeAbiParameters([{ type: 'uint248' }, { type: 'uint256' }], [universeId, ZOLTAR_UNIVERSES_SLOT]))) +} + +function getZoltarUniverseTheoreticalSupplyAttoRepSlot(universeId: bigint) { + return BigInt(keccak256(encodeAbiParameters([{ type: 'uint248' }, { type: 'uint256' }], [universeId, ZOLTAR_UNIVERSE_THEORETICAL_SUPPLIES_SLOT]))) +} + +async function seedAccountBalances(memoryClient: TevmLikeClient, accounts: readonly Address[], onProgress?: BootstrapProgressHandler) { + for (const [index, account] of accounts.entries()) { + await memoryClient.impersonateAccount({ address: account }) + await memoryClient.setBalance({ address: account, value: ETH_BALANCE_AMOUNT }) + await reportBootstrapProgress(onProgress, `Funding QA account ${index + 1} of ${accounts.length}`, 0.05 + ((index + 1) / Math.max(accounts.length, 1)) * 0.08) + } +} + +async function withSimulationAuthorityAccount<TResult>(memoryClient: TevmLikeClient, accountAddress: Address, work: () => Promise<TResult>) { + const originalBalance = await memoryClient.getBalance({ address: accountAddress }) + await memoryClient.impersonateAccount({ address: accountAddress }) + await memoryClient.setBalance({ address: accountAddress, value: ETH_BALANCE_AMOUNT }) + try { + return await work() + } finally { + await memoryClient.setBalance({ address: accountAddress, value: originalBalance }) + } +} + +async function seedGenesisRepTokenState({ + accounts, + createWriteClient, + memoryClient, + onProgress, + repAddress, + zoltarAddress, +}: { + accounts: readonly Address[] + createWriteClient: (accountAddress: Address) => WriteClient + memoryClient: TevmLikeClient + onProgress: BootstrapProgressHandler | undefined + repAddress: Address + zoltarAddress: Address +}) { + const originalNonce = await memoryClient.getTransactionCount({ address: zoltarAddress }) + try { + return await withSimulationAuthorityAccount(memoryClient, zoltarAddress, async () => { + const zoltarWriteClient = createWriteClient(zoltarAddress) + const totalSupply = BigInt(accounts.length) * REP_TOKEN_MINT_AMOUNT + const syncHash = await zoltarWriteClient.writeContract({ + address: repAddress, + abi: ReputationToken_ReputationToken.abi, + functionName: 'setMaxTheoreticalSupplyAttoRep', + args: [totalSupply], + }) + await zoltarWriteClient.waitForTransactionReceipt({ hash: syncHash }) + for (const [index, account] of accounts.entries()) { + const hash = await zoltarWriteClient.writeContract({ + address: repAddress, + abi: ReputationToken_ReputationToken.abi, + functionName: 'mint', + args: [account, REP_TOKEN_MINT_AMOUNT], + }) + await zoltarWriteClient.waitForTransactionReceipt({ hash }) + await reportBootstrapProgress(onProgress, `Seeding REP balances ${index + 1} of ${accounts.length}`, 0.16 + ((index + 1) / Math.max(accounts.length, 1)) * 0.06) + } + await reportBootstrapProgress(onProgress, 'Finalizing REP token state', 0.23) + return totalSupply + }) + } finally { + await memoryClient.setNonce({ address: zoltarAddress, nonce: originalNonce }) + } +} + +export async function updateZoltarGenesisRepToken({ createWriteClient, memoryClient, repAddress, zoltarAddress }: { createWriteClient: (accountAddress: Address) => WriteClient; memoryClient: TevmLikeClient; repAddress: Address; zoltarAddress: Address }) { + const universeBaseSlot = getZoltarUniverseBaseSlot(GENESIS_UNIVERSE_ID) + const readClient = createWriteClient(zoltarAddress) + const genesisTheoreticalSupply = await readClient.readContract({ + address: repAddress, + abi: ReputationToken_ReputationToken.abi, + functionName: 'getTotalTheoreticalSupplyAttoRep', + args: [], + }) + + await memoryClient.setStorageAt({ + address: zoltarAddress, + index: storageIndex(universeBaseSlot + ZOLTAR_GENESIS_REPUTATION_TOKEN_OFFSET), + value: storageValue(BigInt(repAddress)), + }) + await memoryClient.setStorageAt({ + address: zoltarAddress, + index: storageIndex(getZoltarUniverseTheoreticalSupplyAttoRepSlot(GENESIS_UNIVERSE_ID)), + value: storageValue(genesisTheoreticalSupply), + }) + + const patchedRepToken = await readClient.readContract({ + address: zoltarAddress, + abi: Zoltar_Zoltar.abi, + functionName: 'getRepToken', + args: [GENESIS_UNIVERSE_ID], + }) + if (patchedRepToken.toLowerCase() !== repAddress.toLowerCase()) { + throw new Error(`Failed to patch simulation Zoltar genesis REP token. Expected ${repAddress}, received ${patchedRepToken}.`) + } + + const patchedTheoreticalSupply = await readClient.readContract({ + address: zoltarAddress, + abi: Zoltar_Zoltar.abi, + functionName: 'getUniverseTheoreticalSupplyAttoRep', + args: [GENESIS_UNIVERSE_ID], + }) + if (patchedTheoreticalSupply !== genesisTheoreticalSupply) { + throw new Error(`Failed to patch simulation Zoltar theoretical supply. Expected ${genesisTheoreticalSupply.toString()}, received ${patchedTheoreticalSupply.toString()}.`) + } +} + +export async function mintSimulationGenesisRep({ accountAddress, amount, createWriteClient, memoryClient, repAddress, zoltarAddress }: { accountAddress: Address; amount: bigint; createWriteClient: (accountAddress: Address) => WriteClient; memoryClient: TevmLikeClient; repAddress: Address; zoltarAddress: Address }) { + if (amount <= 0n) { + throw new Error('Simulation REP mint amount must be greater than zero') + } + + const originalNonce = await memoryClient.getTransactionCount({ address: zoltarAddress }) + try { + await withSimulationAuthorityAccount(memoryClient, zoltarAddress, async () => { + const zoltarWriteClient = createWriteClient(zoltarAddress) + const totalSupply = await zoltarWriteClient.readContract({ + address: repAddress, + abi: ReputationToken_ReputationToken.abi, + functionName: 'totalSupply', + args: [], + }) + const syncHash = await zoltarWriteClient.writeContract({ + address: repAddress, + abi: ReputationToken_ReputationToken.abi, + functionName: 'setMaxTheoreticalSupplyAttoRep', + args: [totalSupply + amount], + }) + await zoltarWriteClient.waitForTransactionReceipt({ hash: syncHash }) + const mintHash = await zoltarWriteClient.writeContract({ + address: repAddress, + abi: ReputationToken_ReputationToken.abi, + functionName: 'mint', + args: [accountAddress, amount], + }) + await zoltarWriteClient.waitForTransactionReceipt({ hash: mintHash }) + + const zoltarCode = await memoryClient.getCode({ + address: zoltarAddress, + }) + if (zoltarCode === undefined || zoltarCode === '0x') { + return + } + + await updateZoltarGenesisRepToken({ + createWriteClient, + memoryClient, + repAddress, + zoltarAddress, + }) + }) + } finally { + await memoryClient.setNonce({ address: zoltarAddress, nonce: originalNonce }) + } +} + +async function deploySimulationTokens({ + accounts, + createWriteClient, + memoryClient, + onProgress, + primaryAccount, + profile, + zoltarAddress, +}: { + accounts: readonly Address[] + createWriteClient: (accountAddress: Address) => WriteClient + memoryClient: TevmLikeClient + onProgress: BootstrapProgressHandler | undefined + primaryAccount: Address + profile: NetworkProfile + zoltarAddress: Address +}) { + const writeClient = createWriteClient(primaryAccount) + const repDeploymentData = encodeDeployData({ + abi: ReputationToken_ReputationToken.abi, + args: [zoltarAddress], + bytecode: `0x${ReputationToken_ReputationToken.evm.bytecode.object}`, + }) + + await deployContract(writeClient, profile.genesisRepTokenAddress, 'simulation REP token', repDeploymentData) + await reportBootstrapProgress(onProgress, 'Deploying simulation REP token', 0.18) + await memoryClient.setCode({ + address: profile.wethAddress, + bytecode: `0x${statoblast_WETH9_WETH9.evm.deployedBytecode.object}`, + }) + await memoryClient.setStorageAt({ address: profile.wethAddress, index: storageIndex(WETH_NAME_SLOT), value: shortStringStorageValue('Wrapped Ether') }) + await memoryClient.setStorageAt({ address: profile.wethAddress, index: storageIndex(WETH_SYMBOL_SLOT), value: shortStringStorageValue('WETH') }) + await memoryClient.setStorageAt({ address: profile.wethAddress, index: storageIndex(WETH_DECIMALS_SLOT), value: storageValue(18n) }) + await reportBootstrapProgress(onProgress, 'Installing simulation WETH token', 0.2) + await seedGenesisRepTokenState({ + accounts, + createWriteClient, + memoryClient, + onProgress, + repAddress: profile.genesisRepTokenAddress, + zoltarAddress, + }) +} + +export function predictSimulationTokenAddresses(accountAddress: Address): { genesisRepTokenAddress: Address; wethAddress: Address } { + return { + genesisRepTokenAddress: getCreateAddress({ from: accountAddress, nonce: 0n }), + wethAddress: MAINNET_WETH_ADDRESS, + } +} + +async function seedWrappedEthBalances(createWriteClient: (accountAddress: Address) => WriteClient, accounts: readonly Address[], wethAddress: Address, onProgress: BootstrapProgressHandler | undefined) { + for (const [index, account] of accounts.entries()) { + const writeClient = createWriteClient(account) + const hash = await writeClient.sendTransaction({ + to: wethAddress, + value: WETH_TOKEN_MINT_AMOUNT, + }) + await writeClient.waitForTransactionReceipt({ hash }) + await reportBootstrapProgress(onProgress, `Wrapping ETH for QA account ${index + 1} of ${accounts.length}`, 0.24 + ((index + 1) / Math.max(accounts.length, 1)) * 0.06) + } +} + +export async function deploySimulationAppContracts( + primaryWriteClient: WriteClient, + memoryClient: TevmLikeClient, + onProgress: BootstrapProgressHandler | undefined, + profile: NetworkProfile, + range: { start: number; end: number } = { start: 0.32, end: 0.8 }, + getDeploymentSteps: (profile: NetworkProfile) => readonly DeploymentStep[], +) { + const steps = getDeploymentSteps(profile) + for (const [index, step] of steps.entries()) { + const code = await memoryClient.getCode({ address: step.address }) + if (code !== undefined && code !== '0x') { + await reportBootstrapProgress(onProgress, `Checking ${step.label}`, range.start + ((index + 1) / Math.max(steps.length, 1)) * (range.end - range.start)) + continue + } + await step.deploy(primaryWriteClient) + await reportBootstrapProgress(onProgress, `Deploying ${step.label}`, range.start + ((index + 1) / Math.max(steps.length, 1)) * (range.end - range.start)) + } +} + +export type ProgressRange = { + end: number + start: number +} + +export function createRangeProgressReporter(onProgress: BootstrapProgressHandler | undefined, range: ProgressRange, stepCount: number) { + let completedStepCount = 0 + + return async (label: string) => { + completedStepCount += 1 + await reportBootstrapProgress(onProgress, label, range.start + (completedStepCount / Math.max(stepCount, 1)) * (range.end - range.start)) + } +} + +export function requireQaAccount(account: Address | undefined, label: string) { + if (account === undefined) throw new Error(label) + return account +} + +type CoreScenarioApplyParameters = { + accounts: readonly Address[] + createWriteClient: (accountAddress: Address) => WriteClient + getDeploymentSteps: (profile: NetworkProfile) => readonly DeploymentStep[] + memoryClient: TevmLikeClient + onProgress: BootstrapProgressHandler | undefined + profile: NetworkProfile + scenario: SimulationScenario +} + +async function applyCoreScenario({ accounts, createWriteClient, getDeploymentSteps, memoryClient, onProgress, profile, scenario }: CoreScenarioApplyParameters) { + const primaryAccount = requireQaAccount(accounts[0], 'Expected seeded simulation QA account A1') + + switch (scenario) { + case 'baseline': + await reportBootstrapProgress(onProgress, 'Using baseline simulation scenario', 0.84) + return true + case 'deployed': + await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.92 }, getDeploymentSteps) + return true + default: + return false + } +} + +export type BootstrapScenarioApplyParameters = { + accounts: readonly Address[] + createReadClient: () => ReadClient + createWriteClient: (accountAddress: Address) => WriteClient + memoryClient: TevmLikeClient + onProgress: BootstrapProgressHandler | undefined + profile: NetworkProfile + scenario: SimulationScenario +} + +export async function bootstrapSimulationChain({ + accounts, + applyScenario, + createReadClient, + createWriteClient, + getDeploymentSteps, + memoryClient, + onProgress, + primaryAccount, + profile, + scenario, +}: { + accounts: readonly Address[] + applyScenario?: (parameters: BootstrapScenarioApplyParameters) => Promise<boolean> + createReadClient: () => ReadClient + createWriteClient: (accountAddress: Address) => WriteClient + getDeploymentSteps: (profile: NetworkProfile) => readonly DeploymentStep[] + memoryClient: TevmLikeClient + onProgress: BootstrapProgressHandler | undefined + primaryAccount: Address + profile: NetworkProfile + scenario: SimulationScenario +}) { + setRuntimeNetworkProfile(profile) + await reportBootstrapProgress(onProgress, 'Initializing simulation engine', 0.01) + await withTimeout(memoryClient.tevmReady(), 20_000, 'Simulation engine initialization timed out. Firefox may be struggling with main-thread simulation startup.') + await reportBootstrapProgress(onProgress, 'Preparing simulation chain', 0.03) + await initializeSimulationClock(memoryClient) + await seedAccountBalances(memoryClient, accounts, onProgress) + const zoltarStep = getDeploymentSteps(profile).find(step => step.id === 'zoltar') + if (zoltarStep === undefined) throw new Error('Missing Zoltar deployment step for simulation bootstrap') + + await deploySimulationTokens({ + accounts, + createWriteClient, + memoryClient, + onProgress, + primaryAccount, + profile, + zoltarAddress: zoltarStep.address, + }) + await seedWrappedEthBalances(createWriteClient, accounts, profile.wethAddress, onProgress) + const extendedApplied = + applyScenario === undefined + ? false + : await applyScenario({ + accounts, + createReadClient, + createWriteClient, + memoryClient, + onProgress, + profile, + scenario, + }) + const scenarioApplied = + extendedApplied || + (await applyCoreScenario({ + accounts, + createWriteClient, + getDeploymentSteps, + memoryClient, + onProgress, + profile, + scenario, + })) + if (!scenarioApplied) throw new Error(`Unknown simulation scenario: ${scenario}`) + await reportBootstrapProgress(onProgress, 'Saving simulation snapshot', 0.99) + await reportBootstrapProgress(onProgress, 'Simulation scenario ready', 1) +} diff --git a/ui/ts/simulation/clock.ts b/ui/coreShared/ts/simulation/clock.ts similarity index 100% rename from ui/ts/simulation/clock.ts rename to ui/coreShared/ts/simulation/clock.ts diff --git a/ui/ts/simulation/controller.ts b/ui/coreShared/ts/simulation/controller.ts similarity index 100% rename from ui/ts/simulation/controller.ts rename to ui/coreShared/ts/simulation/controller.ts diff --git a/ui/ts/simulation/savedStates.ts b/ui/coreShared/ts/simulation/savedStates.ts similarity index 97% rename from ui/ts/simulation/savedStates.ts rename to ui/coreShared/ts/simulation/savedStates.ts index 37208474f..0d1c7c739 100644 --- a/ui/ts/simulation/savedStates.ts +++ b/ui/coreShared/ts/simulation/savedStates.ts @@ -1,6 +1,6 @@ import { getErrorMessage } from '../lib/errors.js' import { getBrowserStorage } from '../lib/browserStorage.js' -import { isSimulationScenario, type SimulationScenario } from './scenarios.js' +import type { SimulationScenario } from './scenarios.js' const SAVED_SIMULATION_STATES_STORAGE_KEY = 'zoltar.simulation.savedStates' const SAVED_SIMULATION_STATES_CORRUPTED_BACKUP_STORAGE_KEY = 'zoltar.simulation.savedStates.corruptedBackup' @@ -154,7 +154,7 @@ function assertSavedStateEnvelope(value: unknown): asserts value is SavedSimulat if (typeof value['name'] !== 'string' || normalizeSavedStateName(value['name']) === '') throw new SavedSimulationStateError('Saved simulation state is missing a name') if (typeof value['savedAt'] !== 'string' || value['savedAt'].trim() === '') throw new SavedSimulationStateError('Saved simulation state is missing a savedAt timestamp') if (Number.isNaN(Date.parse(value['savedAt']))) throw new SavedSimulationStateError('Saved simulation state is missing a valid savedAt timestamp') - if (typeof value['baseScenario'] !== 'string' || !isSimulationScenario(value['baseScenario'])) throw new SavedSimulationStateError('Saved simulation state is missing a valid baseScenario') + if (typeof value['baseScenario'] !== 'string' || value['baseScenario'] === '') throw new SavedSimulationStateError('Saved simulation state is missing a valid baseScenario') if (!isObjectRecord(value['state'])) throw new SavedSimulationStateError('Saved simulation state is missing its state payload') const state = value['state'] if (typeof state['selectedAccount'] !== 'string' || state['selectedAccount'].trim() === '') throw new SavedSimulationStateError('Saved simulation state is missing a selectedAccount') @@ -174,7 +174,7 @@ function assertSavedStateRecord(value: unknown): asserts value is SavedSimulatio if (typeof value['serialized'] !== 'string' || value['serialized'].trim() === '') throw new SavedSimulationStateError('Saved simulation state record is missing its serialized payload') if (typeof value['name'] !== 'string' || normalizeSavedStateName(value['name']) === '') throw new SavedSimulationStateError('Saved simulation state record is missing a name') if (typeof value['savedAt'] !== 'string' || value['savedAt'].trim() === '') throw new SavedSimulationStateError('Saved simulation state record is missing its savedAt timestamp') - if (typeof value['baseScenario'] !== 'string' || !isSimulationScenario(value['baseScenario'])) throw new SavedSimulationStateError('Saved simulation state record is missing a valid baseScenario') + if (typeof value['baseScenario'] !== 'string' || value['baseScenario'] === '') throw new SavedSimulationStateError('Saved simulation state record is missing a valid baseScenario') if (value['persistedAt'] !== undefined && (typeof value['persistedAt'] !== 'string' || value['persistedAt'].trim() === '' || Number.isNaN(Date.parse(value['persistedAt'])))) { throw new SavedSimulationStateError('Saved simulation state record is missing a valid persistedAt timestamp') } diff --git a/ui/coreShared/ts/simulation/scenarios.ts b/ui/coreShared/ts/simulation/scenarios.ts new file mode 100644 index 000000000..ff5403138 --- /dev/null +++ b/ui/coreShared/ts/simulation/scenarios.ts @@ -0,0 +1,64 @@ +import { assertNever } from '../lib/assert.js' + +export const CORE_SIMULATION_SCENARIOS = ['baseline', 'deployed'] as const + +export type CoreSimulationScenario = (typeof CORE_SIMULATION_SCENARIOS)[number] + +export type SimulationScenario = string + +type ScenarioPresentation = { + description: string + label: string +} + +const scenarioPresentations = new Map<SimulationScenario, ScenarioPresentation>() + +export function registerSimulationScenario(scenario: SimulationScenario, presentation: ScenarioPresentation) { + scenarioPresentations.set(scenario, presentation) +} + +export function getRegisteredSimulationScenarios(): readonly SimulationScenario[] { + return [...CORE_SIMULATION_SCENARIOS, ...scenarioPresentations.keys()] +} + +export function getSimulationScenarioLabel(scenario: SimulationScenario) { + const registered = scenarioPresentations.get(scenario) + if (registered !== undefined) return registered.label + return getCoreSimulationScenarioLabel(scenario as CoreSimulationScenario) +} + +export function getSimulationScenarioDescription(scenario: SimulationScenario) { + const registered = scenarioPresentations.get(scenario) + if (registered !== undefined) return registered.description + return getCoreSimulationScenarioDescription(scenario as CoreSimulationScenario) +} + +export function isCoreSimulationScenario(value: string): value is CoreSimulationScenario { + return (CORE_SIMULATION_SCENARIOS as readonly string[]).includes(value) +} + +export function normalizeSimulationScenario(value: string | undefined): CoreSimulationScenario { + return value !== undefined && isCoreSimulationScenario(value) ? value : 'baseline' +} + +export function getCoreSimulationScenarioLabel(scenario: CoreSimulationScenario) { + switch (scenario) { + case 'baseline': + return 'Baseline' + case 'deployed': + return 'Deployed' + default: + return assertNever(scenario) + } +} + +export function getCoreSimulationScenarioDescription(scenario: CoreSimulationScenario) { + switch (scenario) { + case 'baseline': + return 'Fresh walletless simulation with funded QA accounts and no app contracts deployed. Use it to test the Deploy flow from scratch.' + case 'deployed': + return 'App contracts are deployed, but no security pools or questions are created. Use it to test setup flows from an empty deployment.' + default: + return assertNever(scenario) + } +} diff --git a/ui/ts/simulation/tevmBackend.ts b/ui/coreShared/ts/simulation/tevmBackend.ts similarity index 95% rename from ui/ts/simulation/tevmBackend.ts rename to ui/coreShared/ts/simulation/tevmBackend.ts index b4b0654e1..656da79f8 100644 --- a/ui/ts/simulation/tevmBackend.ts +++ b/ui/coreShared/ts/simulation/tevmBackend.ts @@ -55,10 +55,11 @@ function emitListeners(listeners: ReturnType<typeof createListenerMap>, eventNam } } -function resolveWorkerPath() { +function resolveWorkerPath(appId: 'zoltar' | 'statoblast' | 'trading' = 'zoltar') { const currentUrl = new URL(import.meta.url) - if (currentUrl.protocol === 'file:' && currentUrl.pathname.includes('/ui/ts/')) return new URL('./tevmWorker.ts', import.meta.url) - return new URL('./tevmWorker.worker.js', import.meta.url) + if (currentUrl.protocol === 'file:') return new URL(`../../../${appId}/ts/simulation/tevmWorker.ts`, import.meta.url) + if (currentUrl.pathname.includes('/assets/')) return new URL('./tevmWorker.worker.js', import.meta.url) + return new URL(`../../../${appId}/js/simulation/tevmWorker.worker.js`, import.meta.url) } function createWorkerConnection(workerPath: URL): SimulationWorkerConnection { @@ -93,7 +94,7 @@ function createSimulationProvider(requestRpc: (parameters: RequestArguments) => } export async function createSimulationBackend( - { initialBootstrapError, savedState, savedStateId, scenario }: { initialBootstrapError?: string; savedState?: SavedSimulationStateEnvelopeV1; savedStateId?: string; scenario?: SimulationScenario }, + { appId = 'zoltar', initialBootstrapError, savedState, savedStateId, scenario }: { appId?: 'zoltar' | 'statoblast' | 'trading'; initialBootstrapError?: string; savedState?: SavedSimulationStateEnvelopeV1; savedStateId?: string; scenario?: SimulationScenario }, dependencies: CreateSimulationBackendDependencies = {}, ): Promise<SimulationBackend> { const primaryAccount = QA_ACCOUNTS[0] @@ -111,7 +112,7 @@ export async function createSimulationBackend( scenario: scenario ?? 'baseline', } const listeners = createListenerMap() - const workerPath = resolveWorkerPath() + const workerPath = resolveWorkerPath(appId) const worker = (dependencies.createWorkerConnection ?? createWorkerConnection)(workerPath) const pendingRequests = new Map<number, PendingRequest>() let nextRequestId = 1 diff --git a/ui/ts/simulation/tevmEngine.ts b/ui/coreShared/ts/simulation/tevmEngine.ts similarity index 97% rename from ui/ts/simulation/tevmEngine.ts rename to ui/coreShared/ts/simulation/tevmEngine.ts index c183576cb..6621ea852 100644 --- a/ui/ts/simulation/tevmEngine.ts +++ b/ui/coreShared/ts/simulation/tevmEngine.ts @@ -2,11 +2,10 @@ import { createMemoryClient, type CallParams, type DumpStateResult } from 'tevm' import { createCommon } from 'tevm/common' import { createPublicClient, createWalletClient, custom, encodeFunctionData, parseTransaction, publicActions, recoverTransactionAddress, type Address, type Hash, type Hex } from '@zoltar/shared/ethereum' import { getAddress } from '@zoltar/shared/ethereum' -import { getZoltarAddress } from '../protocol/deploymentHelpers.js' import type { InjectedEthereum } from '../injectedEthereum.js' import type { ChainBackend, CreateWriteClientCallbacks, ReadClient, WriteClient } from '../lib/chainBackend.js' import { createSimulationProfile } from '../lib/networkProfile.js' -import { bootstrapSimulationChain, mintSimulationGenesisRep, predictSimulationTokenAddresses, updateZoltarGenesisRepToken } from './bootstrap.js' +import { bootstrapSimulationChain, mintSimulationGenesisRep, predictSimulationTokenAddresses, updateZoltarGenesisRepToken, type BootstrapScenarioApplyParameters } from './bootstrap.js' import { advanceSimulationTime, getNextSimulationTimestamp, getSimulationChainTimestamp, mineNextSimulationBlock, minePendingSimulationTransactionAtTimestamp } from './clock.js' import type { SimulationScenario } from './scenarios.js' import { serializeSavedSimulationStateEnvelope, type SavedSimulationStateEnvelopeV1, type SimulationInitialization, type SimulationSource } from './savedStates.js' @@ -295,7 +294,13 @@ async function applyDumpedStorageState(memoryClient: MemoryClientLike, address: } } -export async function createSimulationEngine({ initialization }: { initialization: SimulationInitialization }): Promise<SimulationEngine> { +export type SimulationEngineDependencies = { + applyScenario?: (parameters: BootstrapScenarioApplyParameters) => Promise<boolean> + getDeploymentSteps: (profile: ReturnType<typeof createSimulationProfile>) => readonly import('../types/contracts.js').DeploymentStep[] + getZoltarAddress: (profile: ReturnType<typeof createSimulationProfile>) => Address +} + +export async function createSimulationEngine({ initialization, dependencies }: { initialization: SimulationInitialization; dependencies: SimulationEngineDependencies }): Promise<SimulationEngine> { const primaryAccount = QA_ACCOUNTS[0] if (primaryAccount === undefined) throw new Error('No simulation QA accounts configured') const predictedTokenAddresses = predictSimulationTokenAddresses(primaryAccount) @@ -640,8 +645,10 @@ export async function createSimulationEngine({ initialization }: { initializatio const bootstrapBuiltInScenario = async (scenario: SimulationScenario) => { await bootstrapSimulationChain({ accounts: QA_ACCOUNTS, + ...(dependencies.applyScenario === undefined ? {} : { applyScenario: dependencies.applyScenario }), createReadClient: createBootstrapReadClient, createWriteClient: createBootstrapWriteClient, + getDeploymentSteps: dependencies.getDeploymentSteps, memoryClient, onProgress: progress => { bootstrapLabel = progress.label @@ -759,7 +766,7 @@ export async function createSimulationEngine({ initialization }: { initializatio throw new Error('Simulation REP token is unavailable') } - const zoltarAddress = getZoltarAddress(profile) + const zoltarAddress = dependencies.getZoltarAddress(profile) const zoltarCode = await memoryClient.getCode({ address: zoltarAddress, }) diff --git a/ui/ts/simulation/tevmWorker.ts b/ui/coreShared/ts/simulation/tevmWorker.ts similarity index 90% rename from ui/ts/simulation/tevmWorker.ts rename to ui/coreShared/ts/simulation/tevmWorker.ts index 95c0c4c59..f55e3a2d8 100644 --- a/ui/ts/simulation/tevmWorker.ts +++ b/ui/coreShared/ts/simulation/tevmWorker.ts @@ -1,7 +1,11 @@ /// <reference lib="webworker" /> import { assertNever } from '../lib/assert.js' -import { createSimulationEngine } from './tevmEngine.js' +import { createSimulationEngine, type SimulationEngineDependencies } from './tevmEngine.js' + +declare global { + var zoltarSimulationEngineDependencies: SimulationEngineDependencies | undefined +} import type { SimulationWorkerCallMessage, SimulationWorkerEvent, SimulationWorkerMessage, SimulationWorkerResultValue, SimulationWorkerRpcMessage } from './tevmWorkerProtocol.js' function isDedicatedWorkerGlobalScope(value: typeof globalThis): value is typeof globalThis & DedicatedWorkerGlobalScope { @@ -97,7 +101,10 @@ scope.onmessage = event => { try { if (message.type === 'init') { if (enginePromise !== undefined) throw new Error('Simulation worker was already initialized') + const dependencies = globalThis.zoltarSimulationEngineDependencies + if (dependencies === undefined) throw new Error('Simulation engine dependencies were not registered before worker initialization') enginePromise = createSimulationEngine({ + dependencies, initialization: message.initialization, }) const engine = await enginePromise diff --git a/ui/ts/simulation/tevmWorkerProtocol.ts b/ui/coreShared/ts/simulation/tevmWorkerProtocol.ts similarity index 100% rename from ui/ts/simulation/tevmWorkerProtocol.ts rename to ui/coreShared/ts/simulation/tevmWorkerProtocol.ts diff --git a/ui/ts/tests/actionFeedback.test.ts b/ui/coreShared/ts/tests/actionFeedback.test.ts similarity index 100% rename from ui/ts/tests/actionFeedback.test.ts rename to ui/coreShared/ts/tests/actionFeedback.test.ts diff --git a/ui/ts/tests/actionGuards.test.ts b/ui/coreShared/ts/tests/actionGuards.test.ts similarity index 100% rename from ui/ts/tests/actionGuards.test.ts rename to ui/coreShared/ts/tests/actionGuards.test.ts diff --git a/ui/ts/tests/actionLauncherButton.test.tsx b/ui/coreShared/ts/tests/actionLauncherButton.test.tsx similarity index 100% rename from ui/ts/tests/actionLauncherButton.test.tsx rename to ui/coreShared/ts/tests/actionLauncherButton.test.tsx diff --git a/ui/ts/tests/actionLauncherCard.test.tsx b/ui/coreShared/ts/tests/actionLauncherCard.test.tsx similarity index 100% rename from ui/ts/tests/actionLauncherCard.test.tsx rename to ui/coreShared/ts/tests/actionLauncherCard.test.tsx diff --git a/ui/ts/tests/address.test.ts b/ui/coreShared/ts/tests/address.test.ts similarity index 100% rename from ui/ts/tests/address.test.ts rename to ui/coreShared/ts/tests/address.test.ts diff --git a/ui/ts/tests/addressValue.test.tsx b/ui/coreShared/ts/tests/addressValue.test.tsx similarity index 100% rename from ui/ts/tests/addressValue.test.tsx rename to ui/coreShared/ts/tests/addressValue.test.tsx diff --git a/ui/ts/tests/app/appErrorBoundary.test.tsx b/ui/coreShared/ts/tests/app/appErrorBoundary.test.tsx similarity index 100% rename from ui/ts/tests/app/appErrorBoundary.test.tsx rename to ui/coreShared/ts/tests/app/appErrorBoundary.test.tsx diff --git a/ui/ts/tests/app/appHeaderShell.test.tsx b/ui/coreShared/ts/tests/app/appHeaderShell.test.tsx similarity index 65% rename from ui/ts/tests/app/appHeaderShell.test.tsx rename to ui/coreShared/ts/tests/app/appHeaderShell.test.tsx index 1b00dc0ea..f57b6948e 100644 --- a/ui/ts/tests/app/appHeaderShell.test.tsx +++ b/ui/coreShared/ts/tests/app/appHeaderShell.test.tsx @@ -4,66 +4,12 @@ import { describe, expect, mock, test } from 'bun:test' import type { Address } from '@zoltar/shared/ethereum' import { AppHeaderShell } from '../../app/components/AppHeaderShell.js' import type { SimulationController } from '../../simulation/controller.js' -import type { OverviewPanelsProps, TabNavigationProps } from '../../features/types.js' + import { fireEvent, within } from '../testUtils/queries' import { installDomEnvironment } from '../testUtils/domEnvironment.js' +import { installTestRouting } from '../testUtils/testRouting.js' import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' -function createOverviewProps(): OverviewPanelsProps { - return { - accountState: { - address: undefined, - chainId: undefined, - ethBalanceAttoEth: undefined, - wethBalanceAttoEth: undefined, - }, - isConnectingWallet: false, - isManagingWallet: false, - isLoadingRepPrices: false, - isRefreshingRepPrices: false, - isLoadingUniverseRepBalance: false, - isRefreshing: false, - onConnect: () => undefined, - onChangeWallet: () => undefined, - onDisconnectWallet: () => undefined, - onGoToGenesisUniverse: () => undefined, - onRefreshRepPrices: () => undefined, - onSwitchNetwork: () => undefined, - readBackendStatus: { - blockNumber: undefined, - blockTimestamp: undefined, - rpcSource: 'default', - rpcUrl: 'browser-simulation', - transportMode: 'rpc', - }, - repPerEthFailure: undefined, - repPerEthPrice: undefined, - repPerEthSource: undefined, - repPerEthSourceUrl: undefined, - repUsdcFailure: undefined, - repUsdcPrice: undefined, - repUsdcSource: undefined, - repUsdcSourceUrl: undefined, - universeHasForked: false, - universeLabel: 'Genesis', - universePresentation: undefined, - universeRepBalanceAttoRep: undefined, - walletBootstrapComplete: true, - } -} - -function createTabNavigationProps(): TabNavigationProps { - return { - augurStatoblastDeployed: true, - deployRoute: '#/deploy', - marketRoute: '#/zoltar', - onRouteChange: () => undefined, - openOracleRoute: '#/open-oracle', - route: 'zoltar', - securityPoolsRoute: '#/security-pools', - } -} - function createSimulationController(): SimulationController { const selectedAccount = '0x00000000000000000000000000000000000000a1' as Address return { @@ -105,14 +51,15 @@ function createSimulationController(): SimulationController { describe('AppHeaderShell', () => { test('always shows a skip link and focuses app content without changing the hash', async () => { + installTestRouting() const domEnvironment = installDomEnvironment('http://localhost/#/zoltar?simulate=1') const appContent = document.createElement('main') appContent.id = 'app-content' appContent.tabIndex = -1 document.body.appendChild(appContent) - const overview = createOverviewProps() - const tabNavigation = createTabNavigationProps() + const overview = <div>Overview</div> + const tabNavigation = { onRouteChange: () => undefined, route: 'zoltar', tabs: [{ hash: '#/zoltar', label: 'Zoltar', route: 'zoltar' }] } const onRefresh = mock(async () => undefined) const withoutSimulation = await renderIntoDocument(<AppHeaderShell overview={overview} simulationController={undefined} tabNavigation={tabNavigation} onRefresh={onRefresh} />) @@ -139,4 +86,22 @@ describe('AppHeaderShell', () => { domEnvironment.cleanup() } }) + + test('supports an injected application header and custom main-content target', async () => { + const domEnvironment = installDomEnvironment('http://localhost/#/markets') + const appContent = document.createElement('main') + appContent.id = 'main-content' + document.body.appendChild(appContent) + const rendered = await renderIntoDocument(<AppHeaderShell mainElementId='main-content' renderHeader={simulationBanner => <header>{simulationBanner}Trading navigation</header>} simulationController={undefined} onRefresh={async () => undefined} />) + + try { + expect(rendered.container.textContent).toContain('Trading navigation') + fireEvent.click(within(rendered.container).getByRole('button', { name: 'Skip to main content' })) + expect(document.activeElement).toBe(appContent) + } finally { + await rendered.cleanup() + appContent.remove() + domEnvironment.cleanup() + } + }) }) diff --git a/ui/coreShared/ts/tests/app/appRouteGate.test.ts b/ui/coreShared/ts/tests/app/appRouteGate.test.ts new file mode 100644 index 000000000..7f0aabaed --- /dev/null +++ b/ui/coreShared/ts/tests/app/appRouteGate.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, test } from 'bun:test' +import { shouldRenderAppRouteContent } from '../../app/lib/appRouteGate.js' + +describe('shouldRenderAppRouteContent', () => { + test('allows the deployment route through a read-backend error', () => { + expect(shouldRenderAppRouteContent('deploy', 'Wrong network')).toBeTrue() + }) + + test('blocks application routes while the read backend is unavailable', () => { + expect(shouldRenderAppRouteContent('zoltar', 'Wrong network')).toBeFalse() + expect(shouldRenderAppRouteContent('security-pools', 'Wrong network')).toBeFalse() + }) + + test('allows all routes when the read backend is available', () => { + expect(shouldRenderAppRouteContent('not-found', undefined)).toBeTrue() + }) +}) diff --git a/ui/ts/tests/app/appStatusNotices.test.tsx b/ui/coreShared/ts/tests/app/appStatusNotices.test.tsx similarity index 94% rename from ui/ts/tests/app/appStatusNotices.test.tsx rename to ui/coreShared/ts/tests/app/appStatusNotices.test.tsx index 1d92f050d..5510a206d 100644 --- a/ui/ts/tests/app/appStatusNotices.test.tsx +++ b/ui/coreShared/ts/tests/app/appStatusNotices.test.tsx @@ -28,7 +28,7 @@ describe('AppStatusNotices', () => { h(AppStatusNotices, { errorMessage: undefined, readBackendMessage: undefined, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, }), ) @@ -44,7 +44,7 @@ describe('AppStatusNotices', () => { h(AppStatusNotices, { errorMessage: undefined, readBackendMessage: undefined, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, }), ) @@ -60,7 +60,7 @@ describe('AppStatusNotices', () => { h(AppStatusNotices, { errorMessage: undefined, readBackendMessage: undefined, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: 'Anvil boot failed', }), ) @@ -76,7 +76,7 @@ describe('AppStatusNotices', () => { h(AppStatusNotices, { errorMessage: undefined, readBackendMessage: 'Configured read RPC reports chain 11155111, but this app requires Ethereum Mainnet (1).', - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, }), ) @@ -99,7 +99,7 @@ describe('AppStatusNotices', () => { rpcUrl: 'https://query.example/path', transportMode: 'rpc', }, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, }), ) @@ -124,7 +124,7 @@ describe('AppStatusNotices', () => { rpcUrl: 'https://storage.example/path', transportMode: 'rpc', }, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, }), ) @@ -153,7 +153,7 @@ describe('AppStatusNotices', () => { rpcUrl: 'https://ethereum.dark.florist', transportMode: 'provider', }, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, }), ) @@ -170,7 +170,7 @@ describe('AppStatusNotices', () => { h(AppStatusNotices, { errorMessage: 'Top-level error', readBackendMessage: undefined, - showAugurStatoblastDeploymentWarning: true, + showApplicationDeploymentWarning: true, simulationBootstrapError: undefined, }), ) @@ -189,7 +189,7 @@ describe('AppStatusNotices', () => { h(AppStatusNotices, { errorMessages: ['Deployment failed', 'Wallet refresh failed'], readBackendMessage: undefined, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, }), ) @@ -208,7 +208,7 @@ describe('AppStatusNotices', () => { retryCalls += 1 }, readBackendMessage: undefined, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, zoltarUniverseError: 'Universe service unavailable', }), @@ -227,7 +227,7 @@ describe('AppStatusNotices', () => { loadingZoltarUniverse: true, onRetryZoltarUniverse: () => undefined, readBackendMessage: undefined, - showAugurStatoblastDeploymentWarning: false, + showApplicationDeploymentWarning: false, simulationBootstrapError: undefined, zoltarUniverseError: 'Universe service unavailable', }), diff --git a/ui/ts/tests/app/notFoundSection.test.tsx b/ui/coreShared/ts/tests/app/notFoundSection.test.tsx similarity index 100% rename from ui/ts/tests/app/notFoundSection.test.tsx rename to ui/coreShared/ts/tests/app/notFoundSection.test.tsx diff --git a/ui/ts/tests/app/routeSubNavigation.test.tsx b/ui/coreShared/ts/tests/app/routeSubNavigation.test.tsx similarity index 100% rename from ui/ts/tests/app/routeSubNavigation.test.tsx rename to ui/coreShared/ts/tests/app/routeSubNavigation.test.tsx diff --git a/ui/ts/tests/app/supportedNetworkChange.test.ts b/ui/coreShared/ts/tests/app/supportedNetworkChange.test.ts similarity index 100% rename from ui/ts/tests/app/supportedNetworkChange.test.ts rename to ui/coreShared/ts/tests/app/supportedNetworkChange.test.ts diff --git a/ui/ts/tests/app/useHashRoute.test.tsx b/ui/coreShared/ts/tests/app/useHashRoute.test.tsx similarity index 97% rename from ui/ts/tests/app/useHashRoute.test.tsx rename to ui/coreShared/ts/tests/app/useHashRoute.test.tsx index 3e5053f7e..a82dcb059 100644 --- a/ui/ts/tests/app/useHashRoute.test.tsx +++ b/ui/coreShared/ts/tests/app/useHashRoute.test.tsx @@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { act } from 'preact/test-utils' import { useHashRoute } from '../../app/hooks/useHashRoute.js' import { installDomEnvironment } from '../testUtils/domEnvironment.js' +import { installTestRouting } from '../testUtils/testRouting.js' import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' type UseHashRoute = typeof import('../../app/hooks/useHashRoute.js')['useHashRoute'] @@ -27,6 +28,7 @@ describe('useHashRoute', () => { let cleanupRenderedComponent: (() => Promise<void>) | undefined beforeEach(() => { + installTestRouting() cleanupDom = installDomEnvironment('http://localhost/#/zoltar?universe=7&zoltarView=create&simulate=1').cleanup }) diff --git a/ui/ts/tests/app/useOnchainState.integration.test.tsx b/ui/coreShared/ts/tests/app/useOnchainState.integration.test.tsx similarity index 96% rename from ui/ts/tests/app/useOnchainState.integration.test.tsx rename to ui/coreShared/ts/tests/app/useOnchainState.integration.test.tsx index 2ee6c5de1..440b62ba6 100644 --- a/ui/ts/tests/app/useOnchainState.integration.test.tsx +++ b/ui/coreShared/ts/tests/app/useOnchainState.integration.test.tsx @@ -8,14 +8,24 @@ import { act } from 'preact/test-utils' import type { Address } from '@zoltar/shared/ethereum' import { getAddress } from '@zoltar/shared/ethereum' import type { ChainBackend, ReadClient } from '../../lib/chainBackend.js' -import { getDeploymentSteps } from '../../protocol/index.js' import { MAINNET_NETWORK_PROFILE, createSimulationProfile, type NetworkProfile } from '../../lib/networkProfile.js' import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../lib/activeEnvironment.js' import { installDomEnvironment } from '../testUtils/domEnvironment.js' import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' +import type { DeploymentStep } from '../../types/contracts.js' import { useOnchainState, type UseOnchainStateDependencies } from '../../app/hooks/useOnchainState.js' import { formatTimestampWithRelative } from '../../lib/formatters.js' +const FAKE_WETH_ADDRESS = '0x0000000000000000000000000000000000000ee1' as const + +function getDeploymentSteps() { + return [{ address: '0x00000000000000000000000000000000000000d1', dependencies: [], deploy: async () => '0x00000000000000000000000000000000000000000000000000000000000000d1', id: 'zoltar', label: 'Zoltar' }] as const satisfies ReadonlyArray<DeploymentStep> +} + +function getWethAddress() { + return FAKE_WETH_ADDRESS +} + type UseOnchainStateState = ReturnType<typeof useOnchainState> type UseOnchainStateOptions = Parameters<typeof useOnchainState>[0] @@ -139,8 +149,9 @@ function createBackend({ function createOnchainStateDependencies(overrides: Partial<UseOnchainStateDependencies> = {}): UseOnchainStateDependencies { return { getDeploymentSteps, + getWethAddress, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses: getDeploymentSteps().map(step => ({ ...step, deployed: false, @@ -234,7 +245,7 @@ describe('useOnchainState (integration)', () => { readClient: createReadClient({ ethBalanceAttoEth: 123n, blockNumber: 100n, blockTimestamp: 200n }), }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const loadErc20Balance = mock(async () => 555n) @@ -282,7 +293,7 @@ describe('useOnchainState (integration)', () => { readClient: createReadClient({ blockNumber: 300n, blockTimestamp: 400n }), }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const loadErc20Balance = mock(async () => 0n) @@ -362,7 +373,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -416,7 +427,7 @@ describe('useOnchainState (integration)', () => { test('surfaces a blocking error when the configured read RPC is on the wrong chain', async () => { const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const dependencies = createOnchainStateDependencies({ @@ -481,7 +492,7 @@ describe('useOnchainState (integration)', () => { } as ReadClient const { backend } = createBackend({ hasWallet: false, readClient }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), })) const dependencies = createOnchainStateDependencies({ @@ -496,7 +507,7 @@ describe('useOnchainState (integration)', () => { cleanupRenderedComponent = renderedComponent.cleanup await waitFor(() => expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(true)) - expect(requireHookState(hookState).augurStatoblastDeployed).toBe(true) + expect(requireHookState(hookState).applicationDeploymentComplete).toBe(true) expect(loadDeploymentStatusOracleSnapshot).toHaveBeenCalledTimes(1) readChainId = 11155111 @@ -507,7 +518,7 @@ describe('useOnchainState (integration)', () => { expect(requireHookState(hookState).readBackendMessage).toBe('Configured read RPC reports chain 11155111, but this app requires Ethereum Mainnet (1).') expect(requireHookState(hookState).deploymentStatusError).toBe('Deployment status could not be refreshed because read RPC validation failed.') expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(false) - expect(requireHookState(hookState).augurStatoblastDeployed).toBeUndefined() + expect(requireHookState(hookState).applicationDeploymentComplete).toBeUndefined() expect(requireHookState(hookState).deploymentStatuses.every(step => !step.deployed)).toBe(true) expect(loadDeploymentStatusOracleSnapshot).toHaveBeenCalledTimes(1) resetEnvironment() @@ -516,7 +527,7 @@ describe('useOnchainState (integration)', () => { test('keeps RPC-backed reads active when a connected wallet is on the wrong chain', async () => { const account = getAddress('0x00000000000000000000000000000000000000a3') const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const loadErc20Balance = mock(async () => 777n) @@ -633,7 +644,7 @@ describe('useOnchainState (integration)', () => { test('uses the active backend label when surfacing a read-RPC chain mismatch', async () => { const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const dependencies = createOnchainStateDependencies({ @@ -705,7 +716,7 @@ describe('useOnchainState (integration)', () => { loadDeploymentStatusOracleSnapshot: mock(async () => { if (failRefresh) throw new Error('deployment refresh failed') return { - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), } }), @@ -724,7 +735,7 @@ describe('useOnchainState (integration)', () => { await waitFor(() => expect(requireHookState(hookState).accountState.wethBalanceAttoEth).toBe(456n)) expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(true) - expect(requireHookState(hookState).augurStatoblastDeployed).toBe(true) + expect(requireHookState(hookState).applicationDeploymentComplete).toBe(true) failRefresh = true await act(async () => { @@ -735,7 +746,7 @@ describe('useOnchainState (integration)', () => { expect(requireHookState(hookState).accountState.ethBalanceAttoEth).toBeUndefined() expect(requireHookState(hookState).accountState.wethBalanceAttoEth).toBeUndefined() expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(false) - expect(requireHookState(hookState).augurStatoblastDeployed).toBeUndefined() + expect(requireHookState(hookState).applicationDeploymentComplete).toBeUndefined() expect(requireHookState(hookState).deploymentStatuses.every(step => !step.deployed)).toBe(true) resetEnvironment() }) @@ -756,7 +767,7 @@ describe('useOnchainState (integration)', () => { }) const dependencies = createOnchainStateDependencies({ loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), })), loadErc20Balance: mock(async () => 456n), @@ -772,7 +783,7 @@ describe('useOnchainState (integration)', () => { const waitForTrustedState = async () => { await waitFor(() => expect(requireHookState(hookState).accountState.wethBalanceAttoEth).toBe(456n)) expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(true) - expect(requireHookState(hookState).augurStatoblastDeployed).toBe(true) + expect(requireHookState(hookState).applicationDeploymentComplete).toBe(true) } const expectTrustedStateInvalidated = () => { expect(requireHookState(hookState).accountState).toEqual({ @@ -782,7 +793,7 @@ describe('useOnchainState (integration)', () => { wethBalanceAttoEth: undefined, }) expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(false) - expect(requireHookState(hookState).augurStatoblastDeployed).toBeUndefined() + expect(requireHookState(hookState).applicationDeploymentComplete).toBeUndefined() expect(requireHookState(hookState).deploymentStatuses.every(step => !step.deployed)).toBe(true) } @@ -826,7 +837,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -851,7 +862,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -893,7 +904,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 222n), @@ -938,7 +949,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 333n), @@ -987,7 +998,7 @@ describe('useOnchainState (integration)', () => { }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const loadErc20Balance = mock(async () => 0n) @@ -1032,7 +1043,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -1166,7 +1177,7 @@ describe('useOnchainState (integration)', () => { let dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -1203,7 +1214,7 @@ describe('useOnchainState (integration)', () => { dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -1236,7 +1247,7 @@ describe('useOnchainState (integration)', () => { }) const dependencies = createOnchainStateDependencies({ loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), })), }) @@ -1270,7 +1281,7 @@ describe('useOnchainState (integration)', () => { const replacementAccount = getAddress('0x00000000000000000000000000000000000000b2') const replacementAccounts = createDeferred<readonly Address[]>() const replacementSnapshot = createDeferred<{ - augurStatoblastDeployed: boolean + applicationDeploymentComplete: boolean deploymentStatuses: typeof deploymentStatuses }>() const { backend: firstBackend } = createBackend({ @@ -1287,7 +1298,7 @@ describe('useOnchainState (integration)', () => { snapshotLoadCount += 1 if (snapshotLoadCount === 1) return { - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), } return await replacementSnapshot.promise @@ -1329,7 +1340,7 @@ describe('useOnchainState (integration)', () => { expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(false) replacementSnapshot.resolve({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, }) await act(async () => { @@ -1341,7 +1352,7 @@ describe('useOnchainState (integration)', () => { test('does not let an old deployment load hide a replacement environment failure', async () => { const staleSnapshot = createDeferred<{ - augurStatoblastDeployed: boolean + applicationDeploymentComplete: boolean deploymentStatuses: typeof deploymentStatuses }>() const { backend: firstBackend } = createBackend({}) @@ -1374,7 +1385,7 @@ describe('useOnchainState (integration)', () => { expect(requireHookState(hookState).hasLoadedDeploymentStatuses).toBe(false) staleSnapshot.resolve({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), }) await act(async () => { @@ -1396,7 +1407,7 @@ describe('useOnchainState (integration)', () => { } as ReadClient const { backend } = createBackend({ hasWallet: false, readClient }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), })) const dependencies = createOnchainStateDependencies({ @@ -1434,7 +1445,7 @@ describe('useOnchainState (integration)', () => { getAccounts, }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const loadErc20Balance = mock(async () => 0n) @@ -1491,7 +1502,7 @@ describe('useOnchainState (integration)', () => { readClient, }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const loadErc20Balance = mock(async () => wethBalanceAttoEth) @@ -1556,7 +1567,7 @@ describe('useOnchainState (integration)', () => { }) const dependencies = createOnchainStateDependencies({ loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), })), }) @@ -1617,7 +1628,7 @@ describe('useOnchainState (integration)', () => { }) const dependencies = createOnchainStateDependencies({ loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: true, + applicationDeploymentComplete: true, deploymentStatuses: deploymentStatuses.map(step => ({ ...step, deployed: true })), })), }) @@ -1656,7 +1667,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -1691,7 +1702,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps: () => deploymentStatuses, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -1704,13 +1715,13 @@ describe('useOnchainState (integration)', () => { const renderedComponent = await renderIntoDocument(h(Harness, {})) cleanupRenderedComponent = renderedComponent.cleanup - await waitFor(() => expect(requireHookState(hookState).augurStatoblastDeployed).toBe(false)) + await waitFor(() => expect(requireHookState(hookState).applicationDeploymentComplete).toBe(false)) const deployButton = within(document.body).getByRole('button', { name: 'Mark deployments deployed' }) await act(async () => { fireEvent.click(deployButton) }) - await waitFor(() => expect(requireHookState(hookState).augurStatoblastDeployed).toBe(true)) + await waitFor(() => expect(requireHookState(hookState).applicationDeploymentComplete).toBe(true)) expect(requireHookState(hookState).environmentReady).toBe(true) resetEnvironment() }) @@ -1727,7 +1738,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -1765,7 +1776,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 0n), @@ -1814,7 +1825,7 @@ describe('useOnchainState (integration)', () => { readClient, }) const loadDeploymentStatusOracleSnapshot = mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })) const loadErc20Balance = mock(async () => 654n) @@ -1862,7 +1873,7 @@ describe('useOnchainState (integration)', () => { const dependencies = createOnchainStateDependencies({ getDeploymentSteps, loadDeploymentStatusOracleSnapshot: mock(async () => ({ - augurStatoblastDeployed: false, + applicationDeploymentComplete: false, deploymentStatuses, })), loadErc20Balance: mock(async () => 654n), diff --git a/ui/ts/tests/app/useOnchainState.test.ts b/ui/coreShared/ts/tests/app/useOnchainState.test.ts similarity index 97% rename from ui/ts/tests/app/useOnchainState.test.ts rename to ui/coreShared/ts/tests/app/useOnchainState.test.ts index ae09d9be2..2fc192e0b 100644 --- a/ui/ts/tests/app/useOnchainState.test.ts +++ b/ui/coreShared/ts/tests/app/useOnchainState.test.ts @@ -12,6 +12,14 @@ import type { AccountState } from '../../types/app.js' import { installDomEnvironment } from '../testUtils/domEnvironment.js' import { createFakeBackend } from '../testUtils/fakeBackend.js' import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' +import type { UseOnchainStateDependencies } from '../../app/hooks/useOnchainState.js' + +const fakeOnchainStateDependencies: UseOnchainStateDependencies = { + getDeploymentSteps: () => [], + getWethAddress: () => '0x0000000000000000000000000000000000000ee1' as const, + loadDeploymentStatusOracleSnapshot: async () => ({ applicationDeploymentComplete: false, deploymentStatuses: [] }), + loadErc20Balance: async () => 0n, +} function createDeferred<T>() { let resolve: (value: T) => void = () => undefined @@ -457,7 +465,7 @@ void describe('useOnchainState', () => { let cleanupRenderedComponent: (() => Promise<void>) | undefined function OnchainStateHarness() { - const { connectWallet, errorMessage } = useOnchainState() + const { connectWallet, errorMessage } = useOnchainState({}, fakeOnchainStateDependencies) return h('div', {}, [ h( diff --git a/ui/ts/tests/app/useUrlState.test.tsx b/ui/coreShared/ts/tests/app/useUrlState.test.tsx similarity index 98% rename from ui/ts/tests/app/useUrlState.test.tsx rename to ui/coreShared/ts/tests/app/useUrlState.test.tsx index db6d16b7f..014a233f5 100644 --- a/ui/ts/tests/app/useUrlState.test.tsx +++ b/ui/coreShared/ts/tests/app/useUrlState.test.tsx @@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { act } from 'preact/test-utils' import { useUrlState } from '../../app/hooks/useUrlState.js' import { installDomEnvironment } from '../testUtils/domEnvironment.js' +import { installTestRouting } from '../testUtils/testRouting.js' import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' type UseUrlState = typeof import('../../app/hooks/useUrlState.js')['useUrlState'] @@ -30,6 +31,7 @@ describe('useUrlState', () => { let cleanupRenderedComponent: (() => Promise<void>) | undefined beforeEach(() => { + installTestRouting() cleanupDom = installDomEnvironment('http://localhost/#/open-oracle?universe=1&openOracleView=selected-report&openOracleReportId=101&securityPool=0x1111111111111111111111111111111111111111&securityPoolsView=operate&selectedPoolView=positions&zoltarView=trading&questionId=0x42').cleanup }) diff --git a/ui/ts/tests/approvedAmountValueComponent.test.tsx b/ui/coreShared/ts/tests/approvedAmountValueComponent.test.tsx similarity index 100% rename from ui/ts/tests/approvedAmountValueComponent.test.tsx rename to ui/coreShared/ts/tests/approvedAmountValueComponent.test.tsx diff --git a/ui/ts/tests/approvedAmountValueHelpers.test.ts b/ui/coreShared/ts/tests/approvedAmountValueHelpers.test.ts similarity index 100% rename from ui/ts/tests/approvedAmountValueHelpers.test.ts rename to ui/coreShared/ts/tests/approvedAmountValueHelpers.test.ts diff --git a/ui/ts/tests/assert.test.ts b/ui/coreShared/ts/tests/assert.test.ts similarity index 100% rename from ui/ts/tests/assert.test.ts rename to ui/coreShared/ts/tests/assert.test.ts diff --git a/ui/ts/tests/chainBackend.test.ts b/ui/coreShared/ts/tests/chainBackend.test.ts similarity index 100% rename from ui/ts/tests/chainBackend.test.ts rename to ui/coreShared/ts/tests/chainBackend.test.ts diff --git a/ui/ts/tests/simulation/simulationBanner.test.tsx b/ui/coreShared/ts/tests/components/simulationBanner.test.tsx similarity index 98% rename from ui/ts/tests/simulation/simulationBanner.test.tsx rename to ui/coreShared/ts/tests/components/simulationBanner.test.tsx index e9a6e00ff..2816330a6 100644 --- a/ui/ts/tests/simulation/simulationBanner.test.tsx +++ b/ui/coreShared/ts/tests/components/simulationBanner.test.tsx @@ -8,11 +8,26 @@ import type { Address } from '@zoltar/shared/ethereum' import { SimulationBanner } from '../../components/SimulationBanner.js' import type { SimulationController } from '../../simulation/controller.js' import { serializeSavedSimulationStateEnvelope } from '../../simulation/savedStates.js' +import { registerSimulationScenario } from '../../simulation/scenarios.js' +import { installTestRouting } from '../testUtils/testRouting.js' import { installDomEnvironment } from '../testUtils/domEnvironment.js' import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' const SIMULATION_REP_MINT_AMOUNT = 1_000_000n * 10n ** 18n +registerSimulationScenario('security-pool', { + description: 'One seeded question, one security pool, and one funded vault with an active capacity ownership. Use it to test pool actions and liquidation paths.', + label: 'Security pool', +}) +registerSimulationScenario('securitypoolx2', { + description: 'Two security pools sharing a vault with capacity ownership split between them. Use it to test cross-pool liquidation and settlement paths.', + label: 'Security Pool x2', +}) +registerSimulationScenario('securitypoolx2-auction', { + description: 'Two security pools with an in-progress truth auction. Use it to test auction bidding and finalization paths.', + label: 'Security Pool x2 Auction', +}) + function createDeferred<T>() { let resolve: (value: T) => void = () => undefined let reject: (reason?: unknown) => void = () => undefined @@ -102,6 +117,7 @@ function getElementValue(element: Element) { } describe('SimulationBanner', () => { + installTestRouting() test('shows the selected scenario description', async () => { const domEnvironment = installDomEnvironment() const onRefresh = mock(async () => undefined) diff --git a/ui/ts/tests/copyErrorMessage.test.tsx b/ui/coreShared/ts/tests/copyErrorMessage.test.tsx similarity index 100% rename from ui/ts/tests/copyErrorMessage.test.tsx rename to ui/coreShared/ts/tests/copyErrorMessage.test.tsx diff --git a/ui/ts/tests/currencyValue.test.tsx b/ui/coreShared/ts/tests/currencyValue.test.tsx similarity index 100% rename from ui/ts/tests/currencyValue.test.tsx rename to ui/coreShared/ts/tests/currencyValue.test.tsx diff --git a/ui/ts/tests/decimal.test.ts b/ui/coreShared/ts/tests/decimal.test.ts similarity index 100% rename from ui/ts/tests/decimal.test.ts rename to ui/coreShared/ts/tests/decimal.test.ts diff --git a/ui/ts/tests/enumDropdown.test.tsx b/ui/coreShared/ts/tests/enumDropdown.test.tsx similarity index 100% rename from ui/ts/tests/enumDropdown.test.tsx rename to ui/coreShared/ts/tests/enumDropdown.test.tsx diff --git a/ui/ts/tests/errorNotice.test.tsx b/ui/coreShared/ts/tests/errorNotice.test.tsx similarity index 100% rename from ui/ts/tests/errorNotice.test.tsx rename to ui/coreShared/ts/tests/errorNotice.test.tsx diff --git a/ui/ts/tests/errors.test.ts b/ui/coreShared/ts/tests/errors.test.ts similarity index 100% rename from ui/ts/tests/errors.test.ts rename to ui/coreShared/ts/tests/errors.test.ts diff --git a/ui/ts/tests/formatters.test.ts b/ui/coreShared/ts/tests/formatters.test.ts similarity index 100% rename from ui/ts/tests/formatters.test.ts rename to ui/coreShared/ts/tests/formatters.test.ts diff --git a/ui/ts/tests/identifierValue.test.tsx b/ui/coreShared/ts/tests/identifierValue.test.tsx similarity index 100% rename from ui/ts/tests/identifierValue.test.tsx rename to ui/coreShared/ts/tests/identifierValue.test.tsx diff --git a/ui/ts/tests/inputs.test.ts b/ui/coreShared/ts/tests/inputs.test.ts similarity index 100% rename from ui/ts/tests/inputs.test.ts rename to ui/coreShared/ts/tests/inputs.test.ts diff --git a/ui/ts/tests/loadState.test.ts b/ui/coreShared/ts/tests/loadState.test.ts similarity index 100% rename from ui/ts/tests/loadState.test.ts rename to ui/coreShared/ts/tests/loadState.test.ts diff --git a/ui/ts/tests/loadingText.test.tsx b/ui/coreShared/ts/tests/loadingText.test.tsx similarity index 100% rename from ui/ts/tests/loadingText.test.tsx rename to ui/coreShared/ts/tests/loadingText.test.tsx diff --git a/ui/ts/tests/lookupFieldRow.test.tsx b/ui/coreShared/ts/tests/lookupFieldRow.test.tsx similarity index 100% rename from ui/ts/tests/lookupFieldRow.test.tsx rename to ui/coreShared/ts/tests/lookupFieldRow.test.tsx diff --git a/ui/ts/tests/networkProfile.test.ts b/ui/coreShared/ts/tests/networkProfile.test.ts similarity index 100% rename from ui/ts/tests/networkProfile.test.ts rename to ui/coreShared/ts/tests/networkProfile.test.ts diff --git a/ui/ts/tests/noticeStack.test.tsx b/ui/coreShared/ts/tests/noticeStack.test.tsx similarity index 100% rename from ui/ts/tests/noticeStack.test.tsx rename to ui/coreShared/ts/tests/noticeStack.test.tsx diff --git a/ui/ts/tests/operationModal.test.tsx b/ui/coreShared/ts/tests/operationModal.test.tsx similarity index 100% rename from ui/ts/tests/operationModal.test.tsx rename to ui/coreShared/ts/tests/operationModal.test.tsx diff --git a/ui/ts/tests/pagination.test.ts b/ui/coreShared/ts/tests/pagination.test.ts similarity index 100% rename from ui/ts/tests/pagination.test.ts rename to ui/coreShared/ts/tests/pagination.test.ts diff --git a/ui/ts/tests/protocolConfig.test.ts b/ui/coreShared/ts/tests/protocolConfig.test.ts similarity index 100% rename from ui/ts/tests/protocolConfig.test.ts rename to ui/coreShared/ts/tests/protocolConfig.test.ts diff --git a/ui/ts/tests/requestGuard.test.tsx b/ui/coreShared/ts/tests/requestGuard.test.tsx similarity index 100% rename from ui/ts/tests/requestGuard.test.tsx rename to ui/coreShared/ts/tests/requestGuard.test.tsx diff --git a/ui/ts/tests/required.test.ts b/ui/coreShared/ts/tests/required.test.ts similarity index 100% rename from ui/ts/tests/required.test.ts rename to ui/coreShared/ts/tests/required.test.ts diff --git a/ui/ts/tests/routing.test.ts b/ui/coreShared/ts/tests/routing.test.ts similarity index 84% rename from ui/ts/tests/routing.test.ts rename to ui/coreShared/ts/tests/routing.test.ts index 999f5e504..ae8355024 100644 --- a/ui/ts/tests/routing.test.ts +++ b/ui/coreShared/ts/tests/routing.test.ts @@ -1,13 +1,20 @@ /// <reference types="bun-types" /> import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { DEPLOY_ROUTE, OPEN_ORACLE_ROUTE, buildRouteHref, ensureRouteHash, getCurrentRoute, getCurrentRouteHash, getRouteHash, getRouteHashSearch, getTopLevelRouteSearch, SECURITY_POOLS_ROUTE, ZOLTAR_ROUTE } from '../lib/routing.js' +import { buildRouteHref, ensureRouteHash, getCurrentRoute, getCurrentRouteHash, getRouteHash, getRouteHashSearch, getTopLevelRouteSearch } from '../lib/routing.js' import { installDomEnvironment } from './testUtils/domEnvironment.js' +import { installTestRouting } from './testUtils/testRouting.js' + +const DEPLOY_ROUTE = '#/deploy' +const OPEN_ORACLE_ROUTE = '#/open-oracle' +const SECURITY_POOLS_ROUTE = '#/security-pools' +const ZOLTAR_ROUTE = '#/zoltar' describe('routing', () => { let cleanup: (() => void) | undefined beforeEach(() => { + installTestRouting() cleanup = installDomEnvironment('http://localhost/#/zoltar?zoltarView=create&simulate=1').cleanup }) diff --git a/ui/ts/tests/rpcConfig.test.ts b/ui/coreShared/ts/tests/rpcConfig.test.ts similarity index 100% rename from ui/ts/tests/rpcConfig.test.ts rename to ui/coreShared/ts/tests/rpcConfig.test.ts diff --git a/ui/ts/tests/sectionBlock.test.tsx b/ui/coreShared/ts/tests/sectionBlock.test.tsx similarity index 97% rename from ui/ts/tests/sectionBlock.test.tsx rename to ui/coreShared/ts/tests/sectionBlock.test.tsx index bb574f557..b22d719c4 100644 --- a/ui/ts/tests/sectionBlock.test.tsx +++ b/ui/coreShared/ts/tests/sectionBlock.test.tsx @@ -44,7 +44,7 @@ describe('SectionBlock', () => { }) test('keeps embedded spacing overrides after compact and mobile section padding rules', () => { - const cssSource = readFileSync('ui/css/index.css', 'utf8') + const cssSource = readFileSync('ui/coreShared/css/index.css', 'utf8') const compactRuleIndex = cssSource.indexOf('.section-block.density-compact {') const compactEmbeddedRuleIndex = cssSource.indexOf('.section-block.embedded.density-compact {') const mobileSectionRuleIndex = cssSource.indexOf('.route-header,\n\t.section-block,\n\t.overview-panel {') diff --git a/ui/ts/tests/simulation/simulationClock.test.ts b/ui/coreShared/ts/tests/simulation/clock.test.ts similarity index 100% rename from ui/ts/tests/simulation/simulationClock.test.ts rename to ui/coreShared/ts/tests/simulation/clock.test.ts diff --git a/ui/ts/tests/simulation/savedSimulationStates.test.ts b/ui/coreShared/ts/tests/simulation/savedStates.test.ts similarity index 100% rename from ui/ts/tests/simulation/savedSimulationStates.test.ts rename to ui/coreShared/ts/tests/simulation/savedStates.test.ts diff --git a/ui/coreShared/ts/tests/simulation/scenarios.test.ts b/ui/coreShared/ts/tests/simulation/scenarios.test.ts new file mode 100644 index 000000000..74df23567 --- /dev/null +++ b/ui/coreShared/ts/tests/simulation/scenarios.test.ts @@ -0,0 +1,27 @@ +/// <reference types="bun-types" /> + +import { describe, expect, test } from 'bun:test' +import { getRegisteredSimulationScenarios, getSimulationScenarioDescription, getSimulationScenarioLabel, normalizeSimulationScenario, registerSimulationScenario } from '../../simulation/scenarios.js' + +void describe('simulation scenarios', () => { + void test('normalizes core scenarios and rejects unknown values', () => { + expect(normalizeSimulationScenario('baseline')).toBe('baseline') + expect(normalizeSimulationScenario('deployed')).toBe('deployed') + expect(normalizeSimulationScenario('securitypoolx2')).toBe('baseline') + expect(normalizeSimulationScenario(undefined)).toBe('baseline') + }) + + void test('returns labels and descriptions for core scenarios', () => { + expect(getSimulationScenarioLabel('baseline')).toBe('Baseline') + expect(getSimulationScenarioLabel('deployed')).toBe('Deployed') + expect(getSimulationScenarioDescription('baseline')).toBe('Fresh walletless simulation with funded QA accounts and no app contracts deployed. Use it to test the Deploy flow from scratch.') + expect(getSimulationScenarioDescription('deployed')).toBe('App contracts are deployed, but no security pools or questions are created. Use it to test setup flows from an empty deployment.') + }) + + void test('registered app scenarios participate in listing, labels and descriptions', () => { + registerSimulationScenario('securitypoolx2', { description: 'x2 description', label: 'Security pool x2' }) + expect(getRegisteredSimulationScenarios()).toContain('securitypoolx2') + expect(getSimulationScenarioLabel('securitypoolx2')).toBe('Security pool x2') + expect(getSimulationScenarioDescription('securitypoolx2')).toBe('x2 description') + }) +}) diff --git a/ui/ts/tests/simulation/testUtils.ts b/ui/coreShared/ts/tests/simulation/testUtils.ts similarity index 88% rename from ui/ts/tests/simulation/testUtils.ts rename to ui/coreShared/ts/tests/simulation/testUtils.ts index fda2bb28c..cd7944960 100644 --- a/ui/ts/tests/simulation/testUtils.ts +++ b/ui/coreShared/ts/tests/simulation/testUtils.ts @@ -8,10 +8,10 @@ export function activateSimulationBackendProfile(backend: SimulationBackend) { setRuntimeNetworkProfile(backend.profile) } -export async function createBootstrappedSimulationBackendWithRetry(scenario: SimulationScenario, maxAttempts = 2) { +export async function createBootstrappedSimulationBackendWithRetry(scenario: SimulationScenario, maxAttempts = 2, appId: 'zoltar' | 'statoblast' = 'zoltar') { let lastError: unknown = undefined for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { - const backend = await createSimulationBackend({ scenario }) + const backend = await createSimulationBackend({ appId, scenario }) activateSimulationBackendProfile(backend) try { await backend.bootstrap() diff --git a/ui/ts/tests/simulation/tevmBackendWorker.test.ts b/ui/coreShared/ts/tests/simulation/tevmBackendWorker.test.ts similarity index 100% rename from ui/ts/tests/simulation/tevmBackendWorker.test.ts rename to ui/coreShared/ts/tests/simulation/tevmBackendWorker.test.ts diff --git a/ui/ts/tests/stateHint.test.tsx b/ui/coreShared/ts/tests/stateHint.test.tsx similarity index 100% rename from ui/ts/tests/stateHint.test.tsx rename to ui/coreShared/ts/tests/stateHint.test.tsx diff --git a/ui/ts/tests/surfaceNesting.test.tsx b/ui/coreShared/ts/tests/surfaceNesting.test.tsx similarity index 95% rename from ui/ts/tests/surfaceNesting.test.tsx rename to ui/coreShared/ts/tests/surfaceNesting.test.tsx index 0b531136c..4a3db6274 100644 --- a/ui/ts/tests/surfaceNesting.test.tsx +++ b/ui/coreShared/ts/tests/surfaceNesting.test.tsx @@ -42,7 +42,7 @@ describe('flat nested surfaces', () => { }) test('keeps loaded question previews and timeline rows flat', () => { - const cssSource = readFileSync('ui/css/index.css', 'utf8') + const cssSource = readFileSync('ui/coreShared/css/index.css', 'utf8') const loadedPreviewRule = cssSource.slice(cssSource.indexOf('.loaded-question-preview {'), cssSource.indexOf('.field-inline {')) const timelineItemRule = cssSource.slice(cssSource.indexOf('.question-preview-timeline-item {'), cssSource.indexOf('.question-preview-timeline-label,')) @@ -53,7 +53,7 @@ describe('flat nested surfaces', () => { }) test('aligns workflow actions as rows and keeps share totals with the distribution', () => { - const cssSource = readFileSync('ui/css/index.css', 'utf8') + const cssSource = readFileSync('ui/coreShared/css/index.css', 'utf8') const totalRule = cssSource.slice(cssSource.indexOf('.trading-share-callouts-total {'), cssSource.indexOf('.security-pool-strip-meter {')) expect(cssSource).toContain('.vault-action-launcher-grid {\n\tgrid-template-columns: minmax(0, 1fr)') diff --git a/ui/ts/tests/tabNavigation.test.tsx b/ui/coreShared/ts/tests/tabNavigation.test.tsx similarity index 78% rename from ui/ts/tests/tabNavigation.test.tsx rename to ui/coreShared/ts/tests/tabNavigation.test.tsx index aebda8e50..5bfb586b8 100644 --- a/ui/ts/tests/tabNavigation.test.tsx +++ b/ui/coreShared/ts/tests/tabNavigation.test.tsx @@ -3,21 +3,24 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { h } from 'preact' import { TabNavigation } from '../components/TabNavigation.js' -import { DEPLOY_ROUTE, OPEN_ORACLE_ROUTE, SECURITY_POOLS_ROUTE, ZOLTAR_ROUTE } from '../lib/routing.js' +import type { RouteTabDefinition } from '../types/components.js' import { installDomEnvironment } from './testUtils/domEnvironment.js' import { fireEvent, within } from './testUtils/queries' import { renderIntoDocument } from './testUtils/renderIntoDocument.js' +import { installTestRouting } from './testUtils/testRouting.js' + +const DEFAULT_TABS: readonly RouteTabDefinition[] = [ + { hash: '#/deploy', label: 'Deploy', route: 'deploy' }, + { hash: '#/zoltar', label: 'Zoltar', route: 'zoltar' }, + { hash: '#/security-pools', label: 'Security Pools', route: 'security-pools' }, + { hash: '#/open-oracle', label: 'Open Oracle', route: 'open-oracle' }, +] function createProps(overrides: Partial<Parameters<typeof TabNavigation>[0]> = {}): Parameters<typeof TabNavigation>[0] { return { - augurStatoblastDeployed: true, - deployRoute: DEPLOY_ROUTE, - marketRoute: ZOLTAR_ROUTE, onRouteChange: () => undefined, - openOracleRoute: OPEN_ORACLE_ROUTE, route: 'zoltar', - securityPoolsRoute: SECURITY_POOLS_ROUTE, - showDeployTab: true, + tabs: DEFAULT_TABS, ...overrides, } } @@ -27,6 +30,7 @@ describe('TabNavigation', () => { let cleanupRenderedComponent: (() => Promise<void>) | undefined beforeEach(() => { + installTestRouting() cleanupDom = installDomEnvironment('http://localhost/#/zoltar?universe=7&zoltarView=create&simulate=1').cleanup }) @@ -44,14 +48,12 @@ describe('TabNavigation', () => { const documentQueries = within(document.body) expect(documentQueries.getByRole('navigation', { name: 'Application sections' })).not.toBeNull() expect(documentQueries.getByRole('link', { name: 'Deploy' }).getAttribute('href')).toBe('#/deploy?universe=7&simulate=1') - expect(documentQueries.getByRole('link', { name: 'Zoltar' }).getAttribute('href')).toBe('#/zoltar?universe=7&zoltarView=create&simulate=1') + expect(documentQueries.getByRole('link', { name: 'Zoltar' }).getAttribute('href')).toBe('#/zoltar?universe=7&simulate=1') expect(documentQueries.getByRole('link', { name: 'Zoltar' }).getAttribute('aria-current')).toBe('page') expect(documentQueries.getByRole('link', { name: 'Security Pools' }).getAttribute('href')).toBe('#/security-pools?universe=7&simulate=1') expect(documentQueries.getByRole('link', { name: 'Open Oracle' }).getAttribute('href')).toBe('#/open-oracle?universe=7&simulate=1') expect(documentQueries.getByRole('combobox', { name: 'Current application section' })).not.toBeNull() expect(documentQueries.getByRole('link', { name: 'Protocol Guide' }).getAttribute('href')).toBe('https://augurproject.github.io/zoltar/docs/documentation.html') - expect(documentQueries.queryByRole('link', { name: 'Markets' })).toBeNull() - expect(documentQueries.queryByRole('link', { name: 'Oracle Reports' })).toBeNull() }) test('changes routes from the compact route selector', async () => { @@ -71,35 +73,27 @@ describe('TabNavigation', () => { expect(routeChanges).toEqual(['security-pools']) }) - test('keeps Deploy visible as the current compact route while deployment status is loading', async () => { - const rendered = await renderIntoDocument( - h( - TabNavigation, - createProps({ - route: 'deploy', - showDeployTab: false, - }), - ), - ) + test('keeps the first tab as the current compact route when the route is unknown', async () => { + const rendered = await renderIntoDocument(h(TabNavigation, createProps({ route: 'not-found' }))) cleanupRenderedComponent = rendered.cleanup const routeSelector = within(document.body).getByRole('combobox', { name: 'Current application section' }) if (!(routeSelector instanceof window.HTMLSelectElement)) throw new Error('Expected compact route selector') expect(routeSelector.value).toBe('deploy') expect(routeSelector.selectedOptions[0]?.textContent).toBe('Deploy') - expect(within(document.body).getByRole('link', { name: 'Deploy' }).getAttribute('aria-current')).toBe('page') }) - test('uses the deployment prerequisite copy for disabled application sections', async () => { + test('uses the disabled reason copy for disabled application sections', async () => { + const disabledReason = 'Deploy the application contracts before using this section.' const routeChanges: string[] = [] const rendered = await renderIntoDocument( h( TabNavigation, createProps({ - augurStatoblastDeployed: false, onRouteChange: route => { routeChanges.push(route) }, + tabs: DEFAULT_TABS.map(tab => (tab.route === 'zoltar' ? { ...tab, disabled: true, disabledReason } : tab)), }), ), ) @@ -110,9 +104,9 @@ describe('TabNavigation', () => { expect(zoltarTab.getAttribute('aria-disabled')).toBe('true') expect(zoltarTab.getAttribute('href')).toBeNull() expect(zoltarTab.tabIndex).toBe(0) - expect(zoltarTab.title).toBe('Deploy the application contracts before using this section.') - expect(zoltarTab.getAttribute('aria-description')).toBe('Deploy the application contracts before using this section.') - expect(documentQueries.getByText('Deploy the application contracts before using this section.', { selector: '.mobile-route-select .disabled-reason' })).toBeDefined() + expect(zoltarTab.title).toBe(disabledReason) + expect(zoltarTab.getAttribute('aria-description')).toBe(disabledReason) + expect(documentQueries.getByText(disabledReason, { selector: '.mobile-route-select .disabled-reason' })).toBeDefined() zoltarTab.focus() expect(document.activeElement).toBe(zoltarTab) @@ -126,7 +120,7 @@ describe('TabNavigation', () => { const documentQueries = within(document.body) expect(documentQueries.getByRole('link', { name: 'Deploy' }).getAttribute('href')).toBe('#/deploy?universe=7&simulate=1') - expect(documentQueries.getByRole('link', { name: 'Zoltar' }).getAttribute('href')).toBe('#/zoltar?universe=7&zoltarView=create&simulate=1') + expect(documentQueries.getByRole('link', { name: 'Zoltar' }).getAttribute('href')).toBe('#/zoltar?universe=7&simulate=1') expect(documentQueries.getByRole('link', { name: 'Security Pools' }).getAttribute('href')).toBe('#/security-pools?universe=7&simulate=1') expect(documentQueries.getByRole('link', { name: 'Open Oracle' }).getAttribute('href')).toBe('#/open-oracle?universe=7&simulate=1') }) diff --git a/ui/ts/tests/testUtils/domEnvironment.ts b/ui/coreShared/ts/tests/testUtils/domEnvironment.ts similarity index 100% rename from ui/ts/tests/testUtils/domEnvironment.ts rename to ui/coreShared/ts/tests/testUtils/domEnvironment.ts diff --git a/ui/ts/tests/testUtils/fakeBackend.ts b/ui/coreShared/ts/tests/testUtils/fakeBackend.ts similarity index 100% rename from ui/ts/tests/testUtils/fakeBackend.ts rename to ui/coreShared/ts/tests/testUtils/fakeBackend.ts diff --git a/ui/ts/tests/protocol/testSupport.ts b/ui/coreShared/ts/tests/testUtils/protocolTestSupport.ts similarity index 100% rename from ui/ts/tests/protocol/testSupport.ts rename to ui/coreShared/ts/tests/testUtils/protocolTestSupport.ts diff --git a/ui/ts/tests/testUtils/queries.ts b/ui/coreShared/ts/tests/testUtils/queries.ts similarity index 100% rename from ui/ts/tests/testUtils/queries.ts rename to ui/coreShared/ts/tests/testUtils/queries.ts diff --git a/ui/ts/tests/testUtils/renderIntoDocument.tsx b/ui/coreShared/ts/tests/testUtils/renderIntoDocument.tsx similarity index 100% rename from ui/ts/tests/testUtils/renderIntoDocument.tsx rename to ui/coreShared/ts/tests/testUtils/renderIntoDocument.tsx diff --git a/ui/coreShared/ts/tests/testUtils/testRouting.ts b/ui/coreShared/ts/tests/testUtils/testRouting.ts new file mode 100644 index 000000000..499bca436 --- /dev/null +++ b/ui/coreShared/ts/tests/testUtils/testRouting.ts @@ -0,0 +1,13 @@ +import { installRouting, resetRoutingForTesting } from '../../lib/routing.js' + +const DEFAULT_TEST_ROUTES = [ + { hash: '#/deploy', name: 'deploy' }, + { hash: '#/zoltar', name: 'zoltar' }, + { hash: '#/security-pools', name: 'security-pools', queryParameters: new Set(['questionId', 'securityPool', 'securityPoolAddress', 'securityPoolQuestionId', 'securityPoolsView', 'selectedPoolView']) }, + { hash: '#/open-oracle', name: 'open-oracle', queryParameters: new Set(['openOracleReportId', 'openOracleView']) }, +] as const + +export function installTestRouting() { + installRouting({ defaultRoute: 'zoltar', routes: DEFAULT_TEST_ROUTES }) + return () => resetRoutingForTesting() +} diff --git a/ui/ts/tests/testUtils/transactionActionButton.ts b/ui/coreShared/ts/tests/testUtils/transactionActionButton.ts similarity index 100% rename from ui/ts/tests/testUtils/transactionActionButton.ts rename to ui/coreShared/ts/tests/testUtils/transactionActionButton.ts diff --git a/ui/ts/tests/time.test.ts b/ui/coreShared/ts/tests/time.test.ts similarity index 100% rename from ui/ts/tests/time.test.ts rename to ui/coreShared/ts/tests/time.test.ts diff --git a/ui/ts/tests/timestampValue.test.tsx b/ui/coreShared/ts/tests/timestampValue.test.tsx similarity index 100% rename from ui/ts/tests/timestampValue.test.tsx rename to ui/coreShared/ts/tests/timestampValue.test.tsx diff --git a/ui/ts/tests/tokenApproval.test.ts b/ui/coreShared/ts/tests/tokenApproval.test.ts similarity index 100% rename from ui/ts/tests/tokenApproval.test.ts rename to ui/coreShared/ts/tests/tokenApproval.test.ts diff --git a/ui/ts/tests/tokenApprovalControl.test.tsx b/ui/coreShared/ts/tests/tokenApprovalControl.test.tsx similarity index 100% rename from ui/ts/tests/tokenApprovalControl.test.tsx rename to ui/coreShared/ts/tests/tokenApprovalControl.test.tsx diff --git a/ui/ts/tests/transactionActionButton.test.tsx b/ui/coreShared/ts/tests/transactionActionButton.test.tsx similarity index 100% rename from ui/ts/tests/transactionActionButton.test.tsx rename to ui/coreShared/ts/tests/transactionActionButton.test.tsx diff --git a/ui/ts/tests/transactionHashLink.test.tsx b/ui/coreShared/ts/tests/transactionHashLink.test.tsx similarity index 100% rename from ui/ts/tests/transactionHashLink.test.tsx rename to ui/coreShared/ts/tests/transactionHashLink.test.tsx diff --git a/ui/ts/tests/transactionNetworkValue.test.tsx b/ui/coreShared/ts/tests/transactionNetworkValue.test.tsx similarity index 100% rename from ui/ts/tests/transactionNetworkValue.test.tsx rename to ui/coreShared/ts/tests/transactionNetworkValue.test.tsx diff --git a/ui/coreShared/ts/tests/transactionPresentationHelpers.test.tsx b/ui/coreShared/ts/tests/transactionPresentationHelpers.test.tsx new file mode 100644 index 000000000..799cc1dca --- /dev/null +++ b/ui/coreShared/ts/tests/transactionPresentationHelpers.test.tsx @@ -0,0 +1,20 @@ +import { describe, expect, test } from 'bun:test' +import { getPoolUniverseTransactionRows, humanizeTransactionAction } from '../lib/transactionPresentations.js' + +describe('shared transaction presentation helpers', () => { + test('humanizes camel-case actions with protocol units', () => { + expect(humanizeTransactionAction('depositRepToWethVault')).toBe('Deposit REP To WETH Vault') + }) + + test('builds the common security-pool identity row', () => { + const rows = getPoolUniverseTransactionRows({ securityPoolAddress: `0x${'12'.repeat(20)}`, universeId: 7n }) + expect(rows).toHaveLength(1) + expect(rows?.[0]?.identityKey).toBe('security-pool') + expect(rows?.[0]?.value).toBeDefined() + }) + + test('preserves undefined versus an explicitly empty context', () => { + expect(getPoolUniverseTransactionRows(undefined)).toBeUndefined() + expect(getPoolUniverseTransactionRows({})).toEqual([]) + }) +}) diff --git a/ui/ts/tests/transactionReview.test.tsx b/ui/coreShared/ts/tests/transactionReview.test.tsx similarity index 100% rename from ui/ts/tests/transactionReview.test.tsx rename to ui/coreShared/ts/tests/transactionReview.test.tsx diff --git a/ui/ts/tests/transactionStatusCard.test.tsx b/ui/coreShared/ts/tests/transactionStatusCard.test.tsx similarity index 100% rename from ui/ts/tests/transactionStatusCard.test.tsx rename to ui/coreShared/ts/tests/transactionStatusCard.test.tsx diff --git a/ui/ts/tests/transactionTray.test.ts b/ui/coreShared/ts/tests/transactionTray.test.ts similarity index 100% rename from ui/ts/tests/transactionTray.test.ts rename to ui/coreShared/ts/tests/transactionTray.test.ts diff --git a/ui/ts/tests/urlParams.test.ts b/ui/coreShared/ts/tests/urlParams.test.ts similarity index 100% rename from ui/ts/tests/urlParams.test.ts rename to ui/coreShared/ts/tests/urlParams.test.ts diff --git a/ui/ts/tests/useCopyToClipboard.test.tsx b/ui/coreShared/ts/tests/useCopyToClipboard.test.tsx similarity index 100% rename from ui/ts/tests/useCopyToClipboard.test.tsx rename to ui/coreShared/ts/tests/useCopyToClipboard.test.tsx diff --git a/ui/ts/tests/userCopy.test.ts b/ui/coreShared/ts/tests/userCopy.test.ts similarity index 100% rename from ui/ts/tests/userCopy.test.ts rename to ui/coreShared/ts/tests/userCopy.test.ts diff --git a/ui/ts/tests/viewState.test.ts b/ui/coreShared/ts/tests/viewState.test.ts similarity index 100% rename from ui/ts/tests/viewState.test.ts rename to ui/coreShared/ts/tests/viewState.test.ts diff --git a/ui/ts/tests/viewTabs.test.tsx b/ui/coreShared/ts/tests/viewTabs.test.tsx similarity index 100% rename from ui/ts/tests/viewTabs.test.tsx rename to ui/coreShared/ts/tests/viewTabs.test.tsx diff --git a/ui/ts/tests/visualMetrics.test.ts b/ui/coreShared/ts/tests/visualMetrics.test.ts similarity index 100% rename from ui/ts/tests/visualMetrics.test.ts rename to ui/coreShared/ts/tests/visualMetrics.test.ts diff --git a/ui/ts/tests/walletAsset.test.ts b/ui/coreShared/ts/tests/walletAsset.test.ts similarity index 100% rename from ui/ts/tests/walletAsset.test.ts rename to ui/coreShared/ts/tests/walletAsset.test.ts diff --git a/ui/ts/tests/walletAssetControl.test.tsx b/ui/coreShared/ts/tests/walletAssetControl.test.tsx similarity index 100% rename from ui/ts/tests/walletAssetControl.test.tsx rename to ui/coreShared/ts/tests/walletAssetControl.test.tsx diff --git a/ui/ts/tests/writeAction.test.ts b/ui/coreShared/ts/tests/writeAction.test.ts similarity index 100% rename from ui/ts/tests/writeAction.test.ts rename to ui/coreShared/ts/tests/writeAction.test.ts diff --git a/ui/coreShared/ts/types/app.ts b/ui/coreShared/ts/types/app.ts new file mode 100644 index 000000000..5e89f6f17 --- /dev/null +++ b/ui/coreShared/ts/types/app.ts @@ -0,0 +1,30 @@ +import type { Address, Hash } from '@zoltar/shared/ethereum' +import type { GlobalTransactionPresentation, TransactionIntent } from './components.js' +import type { TransactionRequestPreview } from '../lib/chainBackend.js' + +export type RefreshStateOptions = { + loadChainClock?: boolean + loadDeploymentState?: boolean + loadWalletState?: boolean +} + +type RefreshState = (options?: RefreshStateOptions) => Promise<void> + +export type WriteOperationsParameters = { + accountAddress: Address | undefined + onTransactionCanceled?: () => void + onTransactionFailed?: (message: string) => void + onTransactionFinished: () => void + onTransactionPresented: (presentation: GlobalTransactionPresentation) => void + onTransactionPrepared?: (preview: TransactionRequestPreview) => void + onTransactionRequested: (intent: TransactionIntent) => void + onTransactionSubmitted: (hash: Hash) => void + refreshState: RefreshState +} + +export type AccountState = { + address: Address | undefined + chainId: string | undefined + ethBalanceAttoEth: bigint | undefined + wethBalanceAttoEth: bigint | undefined +} diff --git a/ui/ts/types/components.ts b/ui/coreShared/ts/types/components.ts similarity index 82% rename from ui/ts/types/components.ts rename to ui/coreShared/ts/types/components.ts index 901d89a38..cc90482a5 100644 --- a/ui/ts/types/components.ts +++ b/ui/coreShared/ts/types/components.ts @@ -1,6 +1,5 @@ import type { ComponentChildren } from 'preact' import type { Hash } from '@zoltar/shared/ethereum' -import type { Route } from './app.js' export type ActionAvailability = { disabled: boolean @@ -226,13 +225,54 @@ export type OperationModalProps = { title: ComponentChildren } -export type TabNavigationProps = { - route: Route - showDeployTab?: boolean - augurStatoblastDeployed: boolean - deployRoute: string - marketRoute: string - openOracleRoute: string - securityPoolsRoute: string - onRouteChange: (route: Exclude<Route, 'not-found'>) => void +export type RouteTabDefinition = { + disabled?: boolean + disabledReason?: string + hash: string + label: string + route: string +} + +export type OutcomeChipRowProps = { + className?: string + items: Array<{ + key: string + label: ComponentChildren + tone?: 'default' | 'danger' | 'muted' | 'success' | 'warning' + }> +} + +export type ScalarOutcomePickerProps = { + action?: ComponentChildren + clampExactTickInput?: boolean + details: { + answerUnit?: string + displayValueMax?: bigint + displayValueMin?: bigint + maxValueLabel?: ComponentChildren + minValueLabel?: ComponentChildren + numTicks: bigint + } + disabled?: boolean + isInvalid: boolean + label: ComponentChildren + onInvalidChange: (invalid: boolean) => void + onSelectedTickChange: (tick: string) => void + selectedOutcomeLabel: ComponentChildren + selectedTick: string + selectedTickLabel: ComponentChildren + showMinMax?: boolean +} + +export type OutcomeSelectionListProps = { + className?: string + emptyMessage?: ComponentChildren + items: Array<{ + key: string + label: ComponentChildren + details?: ComponentChildren + disabled?: boolean + selected: boolean + onSelect: () => void + }> } diff --git a/ui/ts/types/contracts.ts b/ui/coreShared/ts/types/contracts.ts similarity index 98% rename from ui/ts/types/contracts.ts rename to ui/coreShared/ts/types/contracts.ts index dd4a94536..9c9348f78 100644 --- a/ui/ts/types/contracts.ts +++ b/ui/coreShared/ts/types/contracts.ts @@ -2,7 +2,7 @@ import type { Address, Hash, Hex } from '@zoltar/shared/ethereum' import type { WriteClient as ClientsWriteClient } from '../lib/clients.js' export type { ReadClient, WriteClient } from '../lib/clients.js' -export type DeploymentStepId = +export type ZoltarDeploymentStepId = | 'proxyDeployer' | 'deploymentStatusOracle' | 'weth' @@ -16,10 +16,8 @@ export type DeploymentStepId = | 'zoltar' | 'shareTokenFactory' | 'priceOracleManagerAndOperatorQueuerFactory' - | 'securityPoolForker' - | 'escalationGameClaimDelegate' - | 'escalationGameFactory' - | 'securityPoolFactory' + +export type DeploymentStepId = ZoltarDeploymentStepId | 'securityPoolForker' | 'escalationGameClaimDelegate' | 'escalationGameFactory' | 'securityPoolFactory' export type MarketType = 'binary' | 'categorical' | 'scalar' export type LiquidationApprovalDetails = { @@ -134,7 +132,7 @@ export type DeploymentStatus = DeploymentStep & { } export type DeploymentStatusSnapshot = { - augurStatoblastDeployed: boolean + applicationDeploymentComplete: boolean deploymentStatuses: DeploymentStatus[] } diff --git a/ui/coreShared/tsconfig.json b/ui/coreShared/tsconfig.json new file mode 100644 index 000000000..ea7c1c63f --- /dev/null +++ b/ui/coreShared/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@zoltar/shared/*": [ + "../../shared/js/*" + ] + }, + "rootDir": "./ts", + "outDir": "./js" + }, + "include": [ + "./ts/**/*.ts", + "./ts/**/*.tsx" + ], + "exclude": [ + "./ts/tests/**/*.ts", + "./ts/tests/**/*.tsx" + ] +} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index a3599b264..000000000 --- a/ui/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "description": "Zoltar protocol operations interface", - "name": "@zoltar/ui", - "private": true, - "repository": {}, - "license": "Unlicense", - "type": "module", - "devDependencies": { - "better-typescript-lib": "2.11.0", - "bun-types": "1.3.11", - "typescript": "5.8.2", - "funtypes": "5.1.1" - }, - "dependencies": { - "@preact/signals": "2.0.1", - "@noble/curves": "2.2.0", - "@noble/hashes": "2.2.0", - "@zoltar/shared": "file:../shared", - "buffer": "6.0.3", - "micro-eth-signer": "0.18.1", - "micro-packed": "0.8.0", - "preact": "10.26.4", - "source-map": "0.6.1", - "tevm": "1.0.0-rc.151" - }, - "overrides": { - "brace-expansion": "5.0.9", - "fast-uri": "3.1.5", - "nanoid": "3.3.18", - "postcss": "8.5.23", - "tmp": "0.2.7", - "ws": "8.21.0" - }, - "scripts": { - "preinstall": "bun ./scripts/link-node-modules.mjs", - "link:shared-modules": "bun ./scripts/link-node-modules.mjs", - "check:shared-dependency": "bun ../scripts/ensure-shared-package-fresh.mts", - "refresh:shared-dependency": "bun ../scripts/ensure-shared-package-fresh.mts --refresh", - "contracts": "cd ../solidity && bun run setup", - "generate:shared-js": "cd .. && bun run shared:build", - "generate:contract-artifact": "cd .. && bun run generate:ui-contract-artifact", - "generate:vendor": "bun --tsconfig-override=tsconfig.vendor.json ./build/vendor.mts", - "generate": "bun run generate:shared-js && bun run generate:contract-artifact && bun run generate:vendor", - "setup": "bun ../scripts/install-frozen.mts . && bun run contracts && bun run refresh:shared-dependency && tsc && bun run build:workers", - "shared": "bun run generate:shared-js", - "build:workers": "bun ./build/workers.mts", - "build": "bun run shared && bun run refresh:shared-dependency && tsc && bun run build:workers", - "build:prod": "bun run shared && bun run refresh:shared-dependency && bun ./build/production.mts", - "build:tests": "bun run shared && bun run refresh:shared-dependency && bun ./build/tests.mts", - "watch": "cd .. && bun run generate && cd ui && bun run check:shared-dependency && tsc --watch", - "serve": "cd .. && bun run generate && cd ui && bun run check:shared-dependency && bun x tsc --project tsconfig.json && bun ./dev-server.ts", - "vendor": "bun run generate:vendor" - } -} diff --git a/ui/start.bat b/ui/start.bat deleted file mode 100644 index 63a2a27ac..000000000 --- a/ui/start.bat +++ /dev/null @@ -1,7 +0,0 @@ -@echo off -pushd "%~dp0.." || exit /b 1 -docker build --target local-runtime -f ui/Dockerfile . -t zoltar-ui && docker run --rm -p 8080:8080 zoltar-ui -set "exit_code=%errorlevel%" -popd -pause -exit /b %exit_code% diff --git a/ui/statoblast/bun.lock b/ui/statoblast/bun.lock new file mode 100644 index 000000000..9185e4e7f --- /dev/null +++ b/ui/statoblast/bun.lock @@ -0,0 +1,1099 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@zoltar/ui-statoblast", + "dependencies": { + "@noble/curves": "2.2.0", + "@noble/hashes": "2.2.0", + "@preact/signals": "2.0.1", + "@zoltar/shared": "file:../../shared", + "@zoltar/ui-core-shared": "file:../coreShared", + "@zoltar/ui-zoltar": "file:../zoltar", + "buffer": "6.0.3", + "micro-eth-signer": "0.18.1", + "micro-packed": "0.8.0", + "preact": "10.26.4", + "source-map": "0.6.1", + "tevm": "1.0.0-rc.151", + }, + "devDependencies": { + "better-typescript-lib": "2.11.0", + "bun-types": "1.3.11", + "funtypes": "5.1.1", + "typescript": "5.8.2", + }, + }, + }, + "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", + "brace-expansion": "5.0.9", + "fast-uri": "3.1.5", + "nanoid": "3.3.18", + "postcss": "8.5.23", + "tmp": "0.2.7", + "ws": "8.21.0", + }, + "packages": { + "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], + + "@ark/schema": ["@ark/schema@0.3.3", "", { "dependencies": { "@ark/util": "0.2.2" } }, "sha512-SA4QggzaKHxkNB+faWmhSJbCPMHmGBCpb6dNG3VIt5VOMZhZJJSD76/tOUzQvvJNzztHkTakrTZea+iKsEDcRQ=="], + + "@ark/util": ["@ark/util@0.2.2", "", {}, "sha512-ryZ4+f3SlReQRH9nTFLK5EeU1Pan5ZfS+ACPSk0ir5uujJouFmvOdnkVfeAJAgeOb3kKmUM9kjelv1cwH2ScZg=="], + + "@effect/schema": ["@effect/schema@0.75.5", "", { "dependencies": { "fast-check": "^3.21.0" }, "peerDependencies": { "effect": "^3.9.2" } }, "sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], + + "@ethereumjs/binarytree": ["@ethereumjs/binarytree@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-szugxzYrQHRvlhbu0BrcW/Kw7tg3K0b1HbKCdwRrFhvz80RbGfML2wqDDZhO5NyvHqWpS+Rt9q+qV9LxOHCP6w=="], + + "@ethereumjs/common": ["@ethereumjs/common@10.1.2", "", { "dependencies": { "@ethereumjs/util": "^10.1.2", "eventemitter3": "^5.0.1" } }, "sha512-whWnhqAxwpDy4zWkM6rqMzb8nioZJpiys01N57+HDyanvK5IRzodV5tdMRDt66PD5vDjl2c9K5UcB039gU2Oyw=="], + + "@ethereumjs/evm": ["@ethereumjs/evm@10.1.2", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.2", "@ethereumjs/common": "^10.1.2", "@ethereumjs/statemanager": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "eventemitter3": "^5.0.1" } }, "sha512-yIslek2pcsUQwBJFeeamN529hbNYBtrSuneSCMn4AM71WAx8vntgDbzYoAJsG/whyB9wOVvzHXubju1JXmaEag=="], + + "@ethereumjs/mpt": ["@ethereumjs/mpt@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-dBlXpkP1ssp+AcUxsJUrY72LZuE1JQEp1AZn5mgmbBcd2Gwkpyi57q4zATlZbxrZUxp/K9UIidgqxQWcOCbo5g=="], + + "@ethereumjs/rlp": ["@ethereumjs/rlp@5.0.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA=="], + + "@ethereumjs/statemanager": ["@ethereumjs/statemanager@10.1.2", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.2", "@ethereumjs/common": "^10.1.2", "@ethereumjs/mpt": "^10.1.2", "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@js-sdsl/ordered-map": "^4.4.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-Pkm9VHF19wxT5c0jB+IDiQBv4W42o0wYSS1NLXeRea0D6CDBu0CLo9ihhnQgM368zXPl6JRbLS+YiG8tJkz4aA=="], + + "@ethereumjs/trie": ["@ethereumjs/trie@6.2.1", "", { "dependencies": { "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", "@types/readable-stream": "^2.3.13", "debug": "^4.3.4", "ethereum-cryptography": "^2.2.1", "lru-cache": "10.1.0", "readable-stream": "^3.6.0" } }, "sha512-MguABMVi/dPtgagK+SuY57rpXFP+Ghr2x+pBDy+e3VmMqUY+WGzFu1QWjBb5/iJ7lINk4CI2Uwsih07Nu9sTSg=="], + + "@ethereumjs/tx": ["@ethereumjs/tx@10.1.2", "", { "dependencies": { "@ethereumjs/common": "^10.1.2", "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-bAYK3YaYkk+auzxGfZSRVDbLHvboJNTx8/tV6jaqgPVlrA1QKEEADDEp/EGz+KI4NQmTGxEtXZ8tV/WjniRNww=="], + + "@ethereumjs/util": ["@ethereumjs/util@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-UPBgXtHHfQugoXOSAoeG3jdmPbl37cwV9y3XqTPAnw8tJj8np14TPV2uc5lOs7C2LMF9Ubn66zyaiYxgwGppng=="], + + "@evmts/zevm": ["@evmts/zevm@0.0.0", "", { "dependencies": { "@ethereumjs/common": "^10.1.1", "@ethereumjs/evm": "^10.1.1", "@ethereumjs/trie": "6.2.1", "@ethereumjs/tx": "^10.1.1", "@noble/curves": "^2.2.0", "@noble/hashes": "^2.2.0" }, "optionalDependencies": { "@evmts/zevm-darwin-arm64": "0.0.0", "@evmts/zevm-darwin-x64": "0.0.0", "@evmts/zevm-freebsd-arm64": "0.0.0", "@evmts/zevm-freebsd-x64": "0.0.0", "@evmts/zevm-linux-arm64-gnu": "0.0.0", "@evmts/zevm-linux-arm64-musl": "0.0.0", "@evmts/zevm-linux-x64-gnu": "0.0.0", "@evmts/zevm-linux-x64-musl": "0.0.0", "@evmts/zevm-win32-arm64-msvc": "0.0.0", "@evmts/zevm-win32-ia32-msvc": "0.0.0", "@evmts/zevm-win32-x64-msvc": "0.0.0" } }, "sha512-YQgEGcHaVSHn1zYk6i3sPbO00UR/2XiBP4IKn2Qj0QWBEkjQZlPCOvxXtxqVF/HolPRKu5OxntcbTwfmivwc5Q=="], + + "@evmts/zevm-darwin-arm64": ["@evmts/zevm-darwin-arm64@0.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-g/vvU0csLbv01oFmhejGOj8UtkpQ3+qSMbr4X5gX1Pgq6+z1AWMTHXaKrxbS2NCVLzMW/W+NWs/3dzttTg6IFg=="], + + "@evmts/zevm-darwin-x64": ["@evmts/zevm-darwin-x64@0.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-KU8FSn/0O9xmfhYr5H28bR6lqsLfKx53P1WU30v/dFNo2W3AgHeH/2MbJcERuO7iNz34nN8L7vnYhW9cL19ryg=="], + + "@evmts/zevm-linux-arm64-gnu": ["@evmts/zevm-linux-arm64-gnu@0.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-cXOB4bDR0sesJzoiPaNIXJ7vF9DsaDnBqqW4Hn7sszCY/pV4uL+CAkoZpMS6q/LEw43LJFuA65kLVABtz+e46g=="], + + "@evmts/zevm-linux-x64-gnu": ["@evmts/zevm-linux-x64-gnu@0.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-a6tpXNchDZzI5mElMZ9XFgt6QRZMlBK2sntxe4cABfNTW7h4/Nwn8rJarfXxi7/jF6QqUuOj5wtWUtwiwThGYQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@latticexyz/block-logs-stream": ["@latticexyz/block-logs-stream@2.2.23", "", { "dependencies": { "@latticexyz/common": "2.2.23", "debug": "^4.3.4", "rxjs": "7.5.5" }, "peerDependencies": { "viem": "2.x" } }, "sha512-PbGw6l+bgBNY/DYCy2uMwpBNoK8Y5Qu2CDe17ZP15ATQLdilNEyYIju+867wonGieW7qfHoZRDIH3iqjLgC6zw=="], + + "@latticexyz/common": ["@latticexyz/common@2.2.23", "", { "dependencies": { "@latticexyz/schema-type": "2.2.23", "@solidity-parser/parser": "^0.16.0", "abitype": "1.0.9", "debug": "^4.3.4", "execa": "^9.5.2", "p-queue": "^7.4.1", "p-retry": "^5.1.2", "prettier": "3.2.5", "prettier-plugin-solidity": "1.3.1" }, "peerDependencies": { "@aws-sdk/client-kms": "3.x", "asn1.js": "5.x", "viem": "2.x" }, "optionalPeers": ["@aws-sdk/client-kms", "asn1.js"] }, "sha512-/+73uTmxce6sHg5vQXDPJHDf3kzGE7XOHUC5fxFEBfUO8dqlDzn6t2UTvuAbh9pWsbAPDFBUoILsod8cnPLduA=="], + + "@latticexyz/config": ["@latticexyz/config@2.2.23", "", { "dependencies": { "@ark/util": "0.2.2", "@latticexyz/common": "2.2.23", "@latticexyz/schema-type": "2.2.23", "find-up": "^6.3.0", "tsx": "^4.19.1" }, "peerDependencies": { "viem": "2.x" } }, "sha512-LB70yEbzf2/5ApTc9ftLugS2Ry7jSoy6oZdVPs15/Vt1kWeKSTsleJf+5vx87tMkcK52DSvjLqfo97EJHVpkUg=="], + + "@latticexyz/protocol-parser": ["@latticexyz/protocol-parser@2.2.23", "", { "dependencies": { "@latticexyz/common": "2.2.23", "@latticexyz/config": "2.2.23", "@latticexyz/schema-type": "2.2.23", "abitype": "1.0.9" }, "peerDependencies": { "viem": "2.x" } }, "sha512-ntZ2ILpMaSz/0Lte41WBooIEjmKiE+uo/nBB1EC8B/v/8KXEqblGn1qdhP053j27WeCsf6M442NVkN2S086qFA=="], + + "@latticexyz/schema-type": ["@latticexyz/schema-type@2.2.23", "", { "dependencies": { "abitype": "1.0.9" }, "peerDependencies": { "viem": "2.x" } }, "sha512-xcT2ku5+THLxQf+oMu5Lv6Q6OheI6Aeoc6TzlfVQSyXdRjReUTFXnSM1CXlG2wSrI8vEu+0A6/LdpEY8XcFlpw=="], + + "@latticexyz/store": ["@latticexyz/store@2.2.23", "", { "dependencies": { "@ark/util": "0.2.2", "@latticexyz/common": "2.2.23", "@latticexyz/config": "2.2.23", "@latticexyz/protocol-parser": "2.2.23", "@latticexyz/schema-type": "2.2.23", "abitype": "1.0.9", "arktype": "2.0.0-beta.6", "debug": "^4.3.4" }, "peerDependencies": { "viem": "2.x" }, "optionalPeers": ["viem"] }, "sha512-xKa16GiCoFIj9ZhIyaZmO9v2RAHkQzJqDSF8nuMI65RHmQIMBGhW0g7Qt+Q5FJX7CJA+LziqzNB9YGU9De7Blw=="], + + "@latticexyz/world": ["@latticexyz/world@2.2.23", "", { "dependencies": { "@ark/util": "0.2.2", "@latticexyz/block-logs-stream": "2.2.23", "@latticexyz/common": "2.2.23", "@latticexyz/config": "2.2.23", "@latticexyz/protocol-parser": "2.2.23", "@latticexyz/schema-type": "2.2.23", "@latticexyz/store": "2.2.23", "abitype": "1.0.9", "arktype": "2.0.0-beta.6", "debug": "^4.3.4" }, "peerDependencies": { "viem": "2.x" }, "optionalPeers": ["viem"] }, "sha512-H0MBsUFpim/zwfZbUfon5uadexzFVJlMiCS6ER9m8L1U4WY0iIEYFmT7EUkQK+NAxbF12119v1+GdW3pPBCmQA=="], + + "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + + "@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="], + + "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], + + "@openzeppelin/contracts": ["@openzeppelin/contracts@5.6.1", "", {}, "sha512-Ly6SlsVJ3mj+b18W3R8gNufB7dTICT105fJhodGAGgyC2oqnBAhqSiNDJ8V8DLY05cCz81GLI0CU5vNYA1EC/w=="], + + "@oxc-project/types": ["@oxc-project/types@0.144.0", "", {}, "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg=="], + + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + + "@ponder/utils": ["@ponder/utils@0.2.18", "", { "peerDependencies": { "typescript": ">=5.0.4", "viem": ">=2" }, "optionalPeers": ["typescript"] }, "sha512-pWacQuohfKlKO440zC39C1l6AH+Vx18mca0cPkxM0YnrB70px5hEwLKrc3fU4DMu72j4pljy/Mvfm95FBouYjw=="], + + "@preact/signals": ["@preact/signals@2.0.1", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-f9p/utMgttPb9bJ+UgRBkTDZ9uQiZfX1/gV3pXGWz+yGNQj8MrnG55Xo8MAG4IHcb5UXQO6tvt9ZlsM4A2j+Rw=="], + + "@preact/signals-core": ["@preact/signals-core@1.14.4", "", {}, "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.4", "", { "os": "none", "cpu": "arm64" }, "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@scure/base": ["@scure/base@2.0.0", "", {}, "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w=="], + + "@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], + + "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@solidity-parser/parser": ["@solidity-parser/parser@0.16.2", "", { "dependencies": { "antlr4ts": "^0.5.0-alpha.4" } }, "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tevm/actions": ["@tevm/actions@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3", "zod": "^4.1.11" } }, "sha512-A1ICMc83VK/cauNu/47QPg5pj5vcqvNjl+5JdXJMJKFTkisYqTOkwujoh2tabiylCdkOQL0LATP+BmUt4xPUuA=="], + + "@tevm/address": ["@tevm/address@1.0.0-rc.151", "", { "dependencies": { "@tevm/errors": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-w86dds+px4s/KHfbgxYheyuEk+ZPCayzLWIUzEU/vkvpR7hXLwbolFmogncBPv4zfFOB95ZnopVWxyP2TEbFug=="], + + "@tevm/base-bundler": ["@tevm/base-bundler@1.0.0-rc.151", "", { "dependencies": { "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/compiler": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/runtime": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0", "effect": "3.21.2", "solc": "0.8.35", "solidity-ast": "^0.4.62" } }, "sha512-d0X95IYnQOQGRYZ7JoFXwQYsmF1ZEtRVtppbNZBsfHqrL2fi4iuvVZBmoFPxROANZh/Seo3/Nlk3xa0bA0LAMw=="], + + "@tevm/block": ["@tevm/block@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-4O4+MkAMZ7p9wYwPJYgZBO3lrdQtD5pF0QQgB0ZeJMsibIPw32cVKa1Kfcn1yiI6HjKwmthbLcCXmofXTsBFOQ=="], + + "@tevm/blockchain": ["@tevm/blockchain@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/block": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-eYmxpMXIanrFalOqK0Zk9ZaYhEnws41p+eGSk54lEgudGbPEFTkKEJb4dK8+gAHIxEu5SYcUFdTXLHtfTVezyA=="], + + "@tevm/bun-plugin": ["@tevm/bun-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "effect": "3.21.2", "solc": "0.8.35" } }, "sha512-YYbFClA/nrxyMO7aLP794FTMDeGM0JxpA3bnfE5vY5IDKubG3Dy4N7joih2DOF47K7NFDRv2OAsR6anToFlDYA=="], + + "@tevm/bundler-cache": ["@tevm/bundler-cache@1.0.0-rc.151", "", { "dependencies": { "@tevm/compiler": "^1.0.0-rc.151", "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0" } }, "sha512-EvB5i73kc4KYKHmtBxhPSsVKmtQL0bf6iDExjmhZC/lTzSh6fxlZh+pbtx08Pdtqlx7K7b1GjQKy5wjO67YYxQ=="], + + "@tevm/client-types": ["@tevm/client-types@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-w4aNcZY0cRP0gVqOUwh15jx8bBayxWRq3pSBpM65a/UfiTPKZsgoE2/CX/wITh4XEnYW+gYZToQ7GYswiZuxMg=="], + + "@tevm/common": ["@tevm/common@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/errors": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-fcDCjn06lZRebXl2N5DB1yOfEob0oJVaOslWX0SCWP//C/3z2aFaJns2RkGU+bpzdkK/K55lJq7/EOyX1rv7hw=="], + + "@tevm/compiler": ["@tevm/compiler@1.0.0-rc.151", "", { "dependencies": { "@tevm/config": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/resolutions": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@types/node": "^25.9.0", "abitype": "^1.1.1", "effect": "3.21.2", "resolve": "^1.22.12", "solc-typed-ast": "^18.2.5", "solidity-ast": "^0.4.62" } }, "sha512-qm/19mpaLJ+W5sRNYxSEbZ2RpioRF+w9fjbIFTh+HMxvDjoh9W71PtzqE4hi3+Dg5ro8yzC91Hg6TG7mHfCCIg=="], + + "@tevm/config": ["@tevm/config@1.0.0-rc.151", "", { "dependencies": { "@effect/schema": "0.75.5", "@tevm/effect": "^1.0.0-rc.151", "@types/node": "^25.9.0", "effect": "3.21.2", "zod": "^4.4.3" } }, "sha512-u1iUCojpPWywfRiukZttKa9TQUEwM1uITt3zkBlFMz1jDdZf9E5+sjW4GgbYQdD9eOZt49WCkvJg7VWkRTz+QQ=="], + + "@tevm/consensus": ["@tevm/consensus@1.0.0-rc.152", "", {}, "sha512-YtWQ5hWv05v7FY+QGyYS/l3jLnZWYSS8DV72lIXJ9mrTj9kLMadJcAIDB6MyaZcqeRmVr2WNETCGhOauAGs8GQ=="], + + "@tevm/contract": ["@tevm/contract@1.0.0-rc.151", "", { "dependencies": { "@tevm/errors": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-e3e1V9AngTGsZq2/a1vx+lJpY3IL7ITLhI799kVjLCvqZlNs9/l9EEt8NSv8FpU1+boM2HZcpsRclU/9IftJYA=="], + + "@tevm/decorators": ["@tevm/decorators@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-gw/sZXtFRp/BjAJT4He2MbsCmrrNfH3AXZgQ36KiA71Vure6xI6ecrZauknXa3GqquX2tglusFg8FG7J3ifTpw=="], + + "@tevm/effect": ["@tevm/effect@1.0.0-rc.151", "", { "dependencies": { "effect": "3.21.2", "jsonc-parser": "^3.3.1", "resolve": "^1.22.12" } }, "sha512-9yayiyXSQOaF6ThbBVqRIXZ6VmEdrAPvQgclyFnNt2UOt5RmrusakVptPLiZllMzANb6LnFxZ9pjWLB1ZsnOPQ=="], + + "@tevm/errors": ["@tevm/errors@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "viem": "^2.49.3" } }, "sha512-F+l837Rl3d+GcvSbQw1/QooSmqU4lYmB94MSlZfH6tQiomGSKcVrI+G4PDqvZQJzVYPbIHgpZsD/DKA+eE4p5g=="], + + "@tevm/esbuild-plugin": ["@tevm/esbuild-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-/bzaQ1BejzsO/yVARb1S5Z1B3kjL0lC4PNHIYi0a/RbSpMmJzOzL1FqSiSvXCQ7ID7peJEEWPiK2XHzDk6+Tag=="], + + "@tevm/evm": ["@tevm/evm@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-syg7wCmMd4cktBK/pgipjsYuStSZhN54A1BGeTKu8yBYcQh+baL5uwz/MEk/pn9+UIYzo2Q7qbdVYPQD3wpfyw=="], + + "@tevm/http-client": ["@tevm/http-client@1.0.0-rc.151", "", { "dependencies": { "@tevm/address": "1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/viem": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-BTHBxCkg1gaSZx6UOpab+oLanXCw2ikxK67HSI/xYuwL3YpigDNG7BmdYSffL81G6tx66Lj1DgZ5M9AQbY6p5A=="], + + "@tevm/jsonrpc": ["@tevm/jsonrpc@1.0.0-rc.151", "", { "dependencies": { "@ponder/utils": "^0.2.18" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-3oCXYzDpBDWRFMK/fFI+othvFFADmbBUiU9IxEN/KhV019kB3+zfQs7g7NFqey5m4c90pEAB//M16RKT9HvXdQ=="], + + "@tevm/logger": ["@tevm/logger@1.0.0-rc.151", "", { "dependencies": { "pino": "^10.3.1" } }, "sha512-asE32mndV+Eb8tO8TbjgAeELiJIu4dDyjpnsBJJ7vuX9mYDyg899E27bXM9ByvzhpCmKbmufmRstAeg+xXjUGA=="], + + "@tevm/memory-client": ["@tevm/memory-client@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/address": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "kzg-wasm": "^1.0.0", "vitest": "^4.1.6" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-l7SG85t5OLZv17Gu73pVUnG+Y7D+WCp5tkYYiMn0INtbHG5q3MU1iaeubXcNvSSyZAxxwHtX/I7J9X5I7GL/tA=="], + + "@tevm/node": ["@tevm/node@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/evm": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-lJLaX1QARvwzWhG6ly/l1ZP6jK5XnBmARUM9dUYCP+9S/owixjd7CrI4k+AVCVBEi9yajT45o4j2qa0Opcl+BQ=="], + + "@tevm/precompiles": ["@tevm/precompiles@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@noble/curves": "^2.2.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-mABFuxsZ2OhQPKIMAocwkT42cD1G9OI3vBGuQ0dbpheHdbm7hmClmazySd1fV22exBcC+IIhgJYdtOQT3mozCw=="], + + "@tevm/predeploys": ["@tevm/predeploys@1.0.0-rc.151", "", { "dependencies": { "@tevm/address": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-f82mrrr0Ast4+vw1S4XfIv38em70smo4K40CGTFJ7w3a2rRtXdwMKnJNTaucNdnhi4yj6Sih4AMnIK/z+g2M3Q=="], + + "@tevm/receipt-manager": ["@tevm/receipt-manager@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-gDoNRMSzQFtI8QT5A0QX3vUnpEeXLeaeIyyKRBgKPC2PfMYmyLz4RK8r27v4XJSQJPyiUvYVzKL9HA7hH+QL0Q=="], + + "@tevm/resolutions": ["@tevm/resolutions@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0", "effect": "3.21.2", "resolve": "^1.22.12" } }, "sha512-KYQ9tokr523usldSLElPqCzbw1FmL/G4RlAfVR7EndTP+kEIo9iDh3KdeZbXlQHgkYNHpIqY6Smm5qQHRtnntg=="], + + "@tevm/rollup-plugin": ["@tevm/rollup-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-hTc/s4Zki9ibWLF2BsY6Z5WFRdvSlFg2w2hRzHNl9YqmxGoKyFsFLKpTXWhSUdAXWKYSO6CreRWHOIxdiHM3Xw=="], + + "@tevm/rspack-plugin": ["@tevm/rspack-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-FAjBW/o667yGGcW0LSuskQcSPj/QqZkuDM4jb3lMWZwPUh50wKBloxouodFMvg7DJlWbUb/jTycEmwNw08QsTw=="], + + "@tevm/runtime": ["@tevm/runtime@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "effect": "3.21.2" } }, "sha512-eZi4g01PPmTIeS6uBfJQX31YxLEXE3LoRV2z63XcKBrEpe8WwAp/M4Ob90+9+qxtd4hS8mv0CkX1JE5eLhTroQ=="], + + "@tevm/server": ["@tevm/server@1.0.0-rc.151", "", { "dependencies": { "@tevm/common": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "commander": "^14.0.3", "zod": "^4.4.3" } }, "sha512-3BBNPReFgKUnycYzQs0YdgnQ+NXXrTyqSUjoPel6MEic4Iaq/VOqpzq16ykgK+zhryEkVloFAz7oSd7QXOlypw=="], + + "@tevm/solc": ["@tevm/solc@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "abitype": "^1.2.4", "solidity-ast": "^0.4.61" } }, "sha512-LYNDMNz9JyS+NBMQJJqeSbzaazrDnJ5r2iFtTISFmdGHCDFoXJVNgCMx5ZmpvhrE0XOPueZQGUri1nV25Jsecg=="], + + "@tevm/state": ["@tevm/state@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/test-utils": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "ethereum-cryptography": "^3.2.0" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-aSgXdKI34+w/8MaQcjZEg0SSt/zjA+BJkYMnekf3LEIvUaGigvjwEODnxECmq6+yLFoQzQ5vcwFQ1zFiCDX0pQ=="], + + "@tevm/sync-storage-persister": ["@tevm/sync-storage-persister@1.0.0-rc.151", "", { "dependencies": { "@tevm/state": "^1.0.0-rc.151" } }, "sha512-w8yYpE9CUyuR9YMLWXIA+5f4ujtXrvx59STW8iwxzm6Ag4/QfJEf6Bna/DwUSLmoh4xeYrZqrQMYVQDDWvJE5A=="], + + "@tevm/test-utils": ["@tevm/test-utils@1.0.0-rc.151", "", { "dependencies": { "@latticexyz/schema-type": "^2.2.23", "@latticexyz/store": "^2.2.23", "@latticexyz/world": "^2.2.23", "@openzeppelin/contracts": "5.6.1", "@ponder/utils": "^0.2.18", "@tevm/common": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/esbuild-plugin": "^1.0.0-rc.151", "@tevm/ts-plugin": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-AwtMQyxuDi12H1isX6B0MA9gliK+3N5XOfpXFEFydXGiGckSg7i9QRGJjkG5zS6Tw/P0bq9f1VOXmkqfzVYBYA=="], + + "@tevm/ts-plugin": ["@tevm/ts-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "effect": "3.21.2", "glob": "^13.0.6", "minimatch": "^10.2.5", "solc": "0.8.35", "solidity-ast": "^0.4.62" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "tevm-gen": "dist/bin/tevm-gen.js" } }, "sha512-MkglYXhuoQ3W8R167JZvR/aOoZVzQ+7/dZvGzpkfXyuG+vuxoYhMRt+MWesJrBwyr3+siCvJh6lolBMkDopHew=="], + + "@tevm/tsconfig": ["@tevm/tsconfig@1.0.0-rc.151", "", {}, "sha512-N0RFm8DhMLAk3T9EExLroDcY8TVfULzJKDQ1ENy5mVfWXwsssIeoKbOk46sx1L9U9LQt1If2w0MSCcNbo4ihFg=="], + + "@tevm/tsupconfig": ["@tevm/tsupconfig@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0" } }, "sha512-O/6V2iZaA95REre+mZ+FmzYkg82A8m0jt0OvZ3/Oc9VSvGPxaaePVymg1u/nCRCR5tolX7zuags4xKDE8FB1FA=="], + + "@tevm/tx": ["@tevm/tx@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0" } }, "sha512-dxHBYtEvGf+8Q4oAirJI6X2pUeueQ6/FPWjbA2cKxRqtEv9+eMCSRcc+wJbLrSnQX0obTgirWxrLqGhfni0YuA=="], + + "@tevm/txpool": ["@tevm/txpool@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0" } }, "sha512-x01SVbqIzYk2oHi/k33nDN33/CCfPse+aq83DIZQ2nVOovhAijGry6rVewxj3yogY23WITluahxiWYndw0J7WA=="], + + "@tevm/unplugin": ["@tevm/unplugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "effect": "3.21.2", "solc": "0.8.35", "unplugin": "^3.0.0", "zod": "^4.4.3" } }, "sha512-vV2mxwehVAaVF7F/q0eIKdgfOa1p9U4/ncaeZV/cQSdA8VuW/i3fTIxLxb2wGcifaMNygZCJIjF1fyE9hdnRFw=="], + + "@tevm/utils": ["@tevm/utils@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/errors": "^1.0.0-rc.151", "abitype": "^1.2.4" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-lwd2Ue0KY9sI1TYLc+eAwE97XHLIrgDTtckWgoGwWfxeeCbI0T1nh6g3Dkm8WYOK11nzqxZCx/6rFV8TNLYU9A=="], + + "@tevm/viem": ["@tevm/viem@1.0.0-rc.151", "", { "dependencies": { "@tevm/decorators": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-9ur1qqgo/Cvv7xri25+PtL57yqXsWMVIdLZmOLoLbPggkRd8xv31weaYd91LrgvxziuRvALr30Ex+q9qOrNfhg=="], + + "@tevm/vite-plugin": ["@tevm/vite-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-5J3jTIj9XsU44w1MzJXk5MJQneV0pbErjf3aH/RlYAie4VUG9q95Lim2hvoOPTavwN3bp3JWW7qHnGDhTPcXLw=="], + + "@tevm/vm": ["@tevm/vm@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/evm": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "eventemitter3": "^5.0.4" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-o7mjWa29//tsrauRuBS31YfyDFp++rneTEPUsuYmNNAD5356KGwqHA8eZuDsehXW7KBhuhtzRMxa2SuAAj8+Fg=="], + + "@tevm/webpack-plugin": ["@tevm/webpack-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" }, "peerDependencies": { "webpack": ">5.0.0" } }, "sha512-6/3IMmTU932xIrp9oaQZDM/7pQIbfipxSpwjq1rLxy/d0RUOiWiaohmy0vvJBl0s4rRnT+nT5rTn4fJ+lCN04Q=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], + + "@types/readable-stream": ["@types/readable-stream@2.3.15", "", { "dependencies": { "@types/node": "*", "safe-buffer": "~5.1.1" } }, "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ=="], + + "@types/retry": ["@types/retry@0.12.1", "", {}, "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g=="], + + "@typescript/lib-decorators": ["@better-typescript-lib/decorators@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-ZL90jrzAhnvDr/fbpJt3CgGZdWvAeoAWznp56/mHJRKrCwsaJ9uCPqU09D21FJkWsmZ2jkz+mwBI9+e5dzwFvg=="], + + "@typescript/lib-dom": ["@better-typescript-lib/dom@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-tt0ZgcgxqwRL3oWa2xgtBnq7zJjBXqrtwQo6hvHEwsKNA5SD65FVrC+b1zTFTGvTLURGDIlq1kn3cB2quurVAA=="], + + "@typescript/lib-es2015": ["@better-typescript-lib/es2015@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Xtg8WhxWAvBawWBOsAF3XurOHa18A7nvqYdfGYz4EXfrDvw32nod6zjf0it7/sDwcGIcms6wyRZyLkI4RP7e8Q=="], + + "@typescript/lib-es2016": ["@better-typescript-lib/es2016@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-RFLgskFU2rt/5esIcWibJu8n9a4EjmP6hCh5kqULb+w5J0eE9gXnhU6IZ685+spY2nPw991IYwnUP5idtfiGjQ=="], + + "@typescript/lib-es2017": ["@better-typescript-lib/es2017@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-bgCJrI6h3d6jvZsnzsbdH7/HVAV2GYrbtiUnBxV/ZTJ+VDiHdSI8CvsjIAVYHmrzLDC7FBTU+B+O8YhQ+fsehA=="], + + "@typescript/lib-es2018": ["@better-typescript-lib/es2018@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-8rwLcK9SqzO6bCsa6fQkCrYtFMAs8b5Oz+Rhuh/RmKD/Ja6XvkuXvsA8k+Zf6LjzEc/1aHk3ooQaoPeyh5wVIw=="], + + "@typescript/lib-es2019": ["@better-typescript-lib/es2019@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-S16xjOz1GJ1N9c7cdk+jywkkKjagQI8ojP9wBABU4876rv6Ng64iYVBG6IOpPcQ3eSIbnWSZVk4h4kCyJt1sKA=="], + + "@typescript/lib-es2020": ["@better-typescript-lib/es2020@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-TZMzKXvPXrxT25nhuA43uUbpBagwi5jjxfBcMPZQwbDfFAemO0oa96tQCw8L5oKxN/uxFLhRZuEwLRLVrtcjFw=="], + + "@typescript/lib-es2021": ["@better-typescript-lib/es2021@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-TCc6rj/uj3SHO9SPmsUqT3uRuZgFF2+ZLsKmA6+vQZDDVBQnelQHuooUhUWz49dh9TzntEI46x+yXen+UHfBGw=="], + + "@typescript/lib-es2022": ["@better-typescript-lib/es2022@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Gu5MihDWyEgEd7gXXmMmh2lwyZDPlJIEU8v7Mfeqlk7BfYmS4Raww7f1WYGlXT01zAUELE9vOQdOb+jWG+9N1A=="], + + "@typescript/lib-es2023": ["@better-typescript-lib/es2023@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Ezo4X5hM5bZx9LTPwexvpn1dq+JO+Od6P661pVjMoFA1eUm13SxGox2YIeISFAnWmJyLrcA9ZDr9+1cYlbeZzA=="], + + "@typescript/lib-es2024": ["@better-typescript-lib/es2024@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-1Gix0YIF/NkCid7W+/bOMivG3vIUYfOfpUNx94UDRmkTHTQa3/Vu/X2Mjes8Cu60fBhcfscMBC6QY0emStK79w=="], + + "@typescript/lib-es5": ["@better-typescript-lib/es5@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Efdc4z0hL+HQvcKmsuKtypYcwREUjm1U17eE9J3oXQ2icjR6bAD5IEgFX2gSV2/Ad1KGeBS9gakLxYvDP1G0eQ=="], + + "@typescript/lib-es6": ["@better-typescript-lib/es6@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-zjl9jCTXwWx0QRSv5ILADM1wEgLq40VzBFURVHXg0D8LYv2KYVA7ICdcJZ2/DxoLhaukJm42mllRPjr/Bylw/A=="], + + "@typescript/lib-esnext": ["@better-typescript-lib/esnext@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-NTWWr05QE2/oes0xEiy84on87gzzEWyk0vwPTVgcuWvgtBwDe9sIeGJQSO4vfc7ZP+s3FPBqeSPiAlB7rZi68w=="], + + "@typescript/lib-scripthost": ["@better-typescript-lib/scripthost@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-XwpujOfKtEn0oXodg8Rzm7ULu4OscK0l88ozWp4/q114k5vVd/1d90Q8nEIlB2RR8eqcGNkBG+Tcaj0qiaaJ1A=="], + + "@typescript/lib-webworker": ["@better-typescript-lib/webworker@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-9jdYsSj7YWGixeVok7xZSV+OHpOxMBEcuw+zPHj2462LS812Xq2Vs5Kk00PoFjVXolgQm6naXDG1Wa+x445BPQ=="], + + "@vitest/expect": ["@vitest/expect@4.1.11", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.11", "", { "dependencies": { "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="], + + "@vitest/runner": ["@vitest/runner@4.1.11", "", { "dependencies": { "@vitest/utils": "4.1.11", "pathe": "^2.0.3" } }, "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog=="], + + "@vitest/spy": ["@vitest/spy@4.1.11", "", {}, "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA=="], + + "@vitest/utils": ["@vitest/utils@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ=="], + + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], + + "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], + + "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], + + "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], + + "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], + + "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], + + "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], + + "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], + + "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], + + "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], + + "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], + + "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], + + "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], + + "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], + + "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], + + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], + + "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + + "@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-core-shared": ["@zoltar/ui-core-shared@file:../coreShared", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "@zoltar/ui-zoltar": ["@zoltar/ui-zoltar@file:../zoltar", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "@zoltar/ui-core-shared": "file:../coreShared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "abitype": ["abitype@1.3.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-fk6Te+bojIFrMvMZrnOO+SxCB+RUksTGOzq/60ZRvs1L+BVzvi2bqt9L3W/17ZLdZsyM1FuYf65P5nlmoiH1Bg=="], + + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], + + "antlr4ts": ["antlr4ts@0.5.0-alpha.4", "", {}, "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ=="], + + "arktype": ["arktype@2.0.0-beta.6", "", { "dependencies": { "@ark/schema": "0.3.3", "@ark/util": "0.2.2" } }, "sha512-tbH5/h0z371sgrJIAhZhH2BcrErWv8uQIPVcLmknJ8ffov5/ZbMNufrQ3hG9avGKTcVnVmdQoPhl1WuKuagqXA=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA=="], + + "better-typescript-lib": ["better-typescript-lib@2.11.0", "", { "dependencies": { "@typescript/lib-decorators": "npm:@better-typescript-lib/decorators@2.11.0", "@typescript/lib-dom": "npm:@better-typescript-lib/dom@2.11.0", "@typescript/lib-es2015": "npm:@better-typescript-lib/es2015@2.11.0", "@typescript/lib-es2016": "npm:@better-typescript-lib/es2016@2.11.0", "@typescript/lib-es2017": "npm:@better-typescript-lib/es2017@2.11.0", "@typescript/lib-es2018": "npm:@better-typescript-lib/es2018@2.11.0", "@typescript/lib-es2019": "npm:@better-typescript-lib/es2019@2.11.0", "@typescript/lib-es2020": "npm:@better-typescript-lib/es2020@2.11.0", "@typescript/lib-es2021": "npm:@better-typescript-lib/es2021@2.11.0", "@typescript/lib-es2022": "npm:@better-typescript-lib/es2022@2.11.0", "@typescript/lib-es2023": "npm:@better-typescript-lib/es2023@2.11.0", "@typescript/lib-es2024": "npm:@better-typescript-lib/es2024@2.11.0", "@typescript/lib-es5": "npm:@better-typescript-lib/es5@2.11.0", "@typescript/lib-es6": "npm:@better-typescript-lib/es6@2.11.0", "@typescript/lib-esnext": "npm:@better-typescript-lib/esnext@2.11.0", "@typescript/lib-scripthost": "npm:@better-typescript-lib/scripthost@2.11.0", "@typescript/lib-webworker": "npm:@better-typescript-lib/webworker@2.11.0" }, "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-dwaPodcG5lVepyeoEmwRSpRqKWOso5CRktnMcPKlE4jjcHLGjkBhZXIMe6EFD9ALG/Ru3kwOO1t6TNNQ6Pf1Jw=="], + + "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "command-exists": ["command-exists@1.2.9", "", {}, "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="], + + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-file": ["detect-file@1.0.0", "", {}, "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.411", "", {}, "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg=="], + + "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], + + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "ethereum-cryptography": ["ethereum-cryptography@3.2.0", "", { "dependencies": { "@noble/ciphers": "1.3.0", "@noble/curves": "1.9.0", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0" } }, "sha512-Urr5YVsalH+Jo0sYkTkv1MyI9bLYZwW8BENZCeE1QYaTHETEYx0Nv/SVsWkSqpYrzweg6d8KMY1wTjH/1m/BIg=="], + + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + + "expand-tilde": ["expand-tilde@2.0.2", "", { "dependencies": { "homedir-polyfill": "^1.0.1" } }, "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw=="], + + "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="], + + "findup-sync": ["findup-sync@5.0.0", "", { "dependencies": { "detect-file": "^1.0.0", "is-glob": "^4.0.3", "micromatch": "^4.0.4", "resolve-dir": "^1.0.1" } }, "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], + + "fs-extra": ["fs-extra@11.4.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "funtypes": ["funtypes@5.1.1", "", {}, "sha512-JdOe6zAk3kzTTuoTKrdshe16mr8FwuNzUnSg5gZpeJxMYFiY0LaIec9A4/VHK8Z3fSnm09eMrH93vaHE2R0J5Q=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "global-modules": ["global-modules@1.0.0", "", { "dependencies": { "global-prefix": "^1.0.1", "is-windows": "^1.0.1", "resolve-dir": "^1.0.0" } }, "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg=="], + + "global-prefix": ["global-prefix@1.0.2", "", { "dependencies": { "expand-tilde": "^2.0.2", "homedir-polyfill": "^1.0.1", "ini": "^1.3.4", "is-windows": "^1.0.1", "which": "^1.2.14" } }, "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + + "homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="], + + "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], + + "js-sha3": ["js-sha3@0.8.0", "", {}, "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="], + + "jsel": ["jsel@1.1.6", "", {}, "sha512-7E6r8kVzjmKhwXR/82Z+43edfOJGRvLvx6cJZ+SS2MGAPPtYZGnaIsFHpQMA1IbIPA9twDProkob4IIAJ0ZqSw=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "kzg-wasm": ["kzg-wasm@1.0.0", "", {}, "sha512-jMvFK5uiV41BgcLrCaHbNeDeQeZdoTcqOYm7yS1Xdvl4MAykjou+SLlnt+P1bWz5C0YWHOmgUUjIpCuU83zNTA=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], + + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "micro-eth-signer": ["micro-eth-signer@0.18.1", "", { "dependencies": { "@noble/curves": "^2.0.0", "@noble/hashes": "^2.0.0", "micro-packed": "^0.8.0" } }, "sha512-vXKhCZxrytpl+dXR9JeaE41ZVFndi7wCKc1Jd22smOMAeDErvRcXaTxhYUf1yQxis4n2kIdv/pH7iuf+5/cj+Q=="], + + "micro-packed": ["micro-packed@0.8.0", "", { "dependencies": { "@scure/base": "2.0.0" } }, "sha512-AKb8znIvg9sooythbXzyFeChEY0SkW0C6iXECpy/ls0e5BtwXO45J9wD9SLzBztnS4XmF/5kwZknsq+jyynd/A=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "minimizer-webpack-plugin": ["minimizer-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "@minify-html/node": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "lightningcss": "*", "postcss": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "lightningcss", "postcss", "uglify-js"] }, "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "ox": ["ox@0.14.34", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-12seOIk7dv8eAoGQhcWaeKZxNz304IVcDvb9U5Y7JZAEVe21Nm1YMxLjhWah+su5BD4Omx4Zz0z5x3ij9M4GYQ=="], + + "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + + "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + + "p-queue": ["p-queue@7.4.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^5.0.2" } }, "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA=="], + + "p-retry": ["p-retry@5.1.2", "", { "dependencies": { "@types/retry": "0.12.1", "retry": "^0.13.1" } }, "sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g=="], + + "p-timeout": ["p-timeout@5.1.0", "", {}, "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "parse-passwd": ["parse-passwd@1.0.0", "", {}, "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q=="], + + "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], + + "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], + + "preact": ["preact@10.26.4", "", {}, "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w=="], + + "prettier": ["prettier@3.2.5", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A=="], + + "prettier-plugin-solidity": ["prettier-plugin-solidity@1.3.1", "", { "dependencies": { "@solidity-parser/parser": "^0.17.0", "semver": "^7.5.4", "solidity-comments-extractor": "^0.0.8" }, "peerDependencies": { "prettier": ">=2.3.0" } }, "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA=="], + + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + + "process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-dir": ["resolve-dir@1.0.1", "", { "dependencies": { "expand-tilde": "^2.0.0", "global-modules": "^1.0.0" } }, "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg=="], + + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "rolldown": ["rolldown@1.2.4", "", { "dependencies": { "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.4", "@rolldown/binding-darwin-arm64": "1.2.4", "@rolldown/binding-darwin-x64": "1.2.4", "@rolldown/binding-freebsd-x64": "1.2.4", "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", "@rolldown/binding-linux-arm64-gnu": "1.2.4", "@rolldown/binding-linux-arm64-musl": "1.2.4", "@rolldown/binding-linux-ppc64-gnu": "1.2.4", "@rolldown/binding-linux-s390x-gnu": "1.2.4", "@rolldown/binding-linux-x64-gnu": "1.2.4", "@rolldown/binding-linux-x64-musl": "1.2.4", "@rolldown/binding-openharmony-arm64": "1.2.4", "@rolldown/binding-win32-arm64-msvc": "1.2.4", "@rolldown/binding-win32-x64-msvc": "1.2.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w=="], + + "rxjs": ["rxjs@7.5.5", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw=="], + + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], + + "semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "solc": ["solc@0.8.36", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-cnDhjWo8dC5FxnEwCrWxUsJS7RPsHf+pVUe9Tr/+b15sQ40WCVn4vX4R+tOvN5gRfEgiquvFzfF4VV0kSnknhw=="], + + "solc-typed-ast": ["solc-typed-ast@18.2.6", "", { "dependencies": { "axios": "^1.6.8", "commander": "^12.0.0", "decimal.js": "^10.4.3", "findup-sync": "^5.0.0", "fs-extra": "^11.2.0", "jsel": "^1.1.6", "semver": "^7.6.0", "solc": "0.8.25", "src-location": "^1.1.0", "web3-eth-abi": "^4.2.0" }, "bin": { "sol-ast-compile": "dist/bin/compile.js" } }, "sha512-JObIRpPYO4zvQkAgr3koBr5Du+IkYzVNofykZpGv/44iJ0VRsaEq/X+LrrFD0SDqFcnyYcBOPWDiOXwNOStKlg=="], + + "solidity-ast": ["solidity-ast@0.4.62", "", {}, "sha512-jSC7msQCkJXIzM8LlDjRZ5cif5w40g6THlXHFk3zchbL5dm3YLoBETvqPGo5KndYkftjhcs5kz1fnTu4d34lVQ=="], + + "solidity-comments-extractor": ["solidity-comments-extractor@0.0.8", "", {}, "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g=="], + + "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "src-location": ["src-location@1.1.0", "", {}, "sha512-idBVZgLZGzB3B2Et317AFDQto7yRgp1tOuFd+VKIH2dw1jO1b6p07zNjtQoVhkW+CY6oGTp9Y5UIfbJoZRsoFQ=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "terser": ["terser@5.50.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w=="], + + "tevm": ["tevm@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/address": "^1.0.0-rc.151", "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/bun-plugin": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/client-types": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/compiler": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/esbuild-plugin": "^1.0.0-rc.151", "@tevm/evm": "^1.0.0-rc.151", "@tevm/http-client": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/rollup-plugin": "^1.0.0-rc.151", "@tevm/rspack-plugin": "^1.0.0-rc.151", "@tevm/server": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/ts-plugin": "^1.0.0-rc.151", "@tevm/tx": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/viem": "^1.0.0-rc.151", "@tevm/vite-plugin": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151", "@tevm/webpack-plugin": "^1.0.0-rc.151", "commander": "^14.0.3", "effect": "3.21.2", "glob": "^13.0.6", "solc": "^0.8.35" }, "peerDependencies": { "viem": "^2.49.3" }, "bin": { "tevm": "cli.js" } }, "sha512-LyFVsmOZUo4ZnFlZa4zgchK1Z2bHiXyihTlh3Ozin6IbgZa2ReJgdDz2I+UI+27IXzrRKGpMLy1WNAWbhaVSJA=="], + + "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tinyrainbow": ["tinyrainbow@3.1.1", "", {}, "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw=="], + + "tmp": ["tmp@0.2.7", "", {}, "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsx": ["tsx@4.23.12", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], + + "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], + + "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "viem": ["viem@2.55.19", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.34", "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-4QPIX0eYPLsOBk53NKswVMkQoxuP7GlOBnB4wM6dkDokREO4QENNc3bmyPKK1PBTViXh0TPJCHLjIuU20Qi3fg=="], + + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], + + "vitest": ["vitest@4.1.11", "", { "dependencies": { "@vitest/expect": "4.1.11", "@vitest/mocker": "4.1.11", "@vitest/pretty-format": "4.1.11", "@vitest/runner": "4.1.11", "@vitest/snapshot": "4.1.11", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.11", "@vitest/browser-preview": "4.1.11", "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-istanbul": "4.1.11", "@vitest/coverage-v8": "4.1.11", "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw=="], + + "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], + + "web3-errors": ["web3-errors@1.3.1", "", { "dependencies": { "web3-types": "^1.10.0" } }, "sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ=="], + + "web3-eth-abi": ["web3-eth-abi@4.4.1", "", { "dependencies": { "abitype": "0.7.1", "web3-errors": "^1.3.1", "web3-types": "^1.10.0", "web3-utils": "^4.3.3", "web3-validator": "^2.0.6" } }, "sha512-60ecEkF6kQ9zAfbTY04Nc9q4eEYM0++BySpGi8wZ2PD1tw/c0SDvsKhV6IKURxLJhsDlb08dATc3iD6IbtWJmg=="], + + "web3-types": ["web3-types@1.10.0", "", {}, "sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw=="], + + "web3-utils": ["web3-utils@4.3.3", "", { "dependencies": { "ethereum-cryptography": "^2.0.0", "eventemitter3": "^5.0.1", "web3-errors": "^1.3.1", "web3-types": "^1.10.0", "web3-validator": "^2.0.6" } }, "sha512-kZUeCwaQm+RNc2Bf1V3BYbF29lQQKz28L0y+FA4G0lS8IxtJVGi5SeDTUkpwqqkdHHC7JcapPDnyyzJ1lfWlOw=="], + + "web3-validator": ["web3-validator@2.0.6", "", { "dependencies": { "ethereum-cryptography": "^2.0.0", "util": "^0.12.5", "web3-errors": "^1.2.0", "web3-types": "^1.6.0", "zod": "^3.21.4" } }, "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg=="], + + "webpack": ["webpack@5.109.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.24.4", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "mime-db": "^1.54.0", "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "watchpack": "^2.5.2", "webpack-sources": "^3.5.1" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw=="], + + "webpack-sources": ["webpack-sources@3.5.1", "", {}, "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + + "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "yoctocolors": ["yoctocolors@2.2.0", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="], + + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@ethereumjs/binarytree/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/binarytree/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], + + "@ethereumjs/mpt/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/mpt/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], + + "@ethereumjs/statemanager/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/statemanager/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], + + "@ethereumjs/trie/@ethereumjs/util": ["@ethereumjs/util@9.1.0", "", { "dependencies": { "@ethereumjs/rlp": "^5.0.2", "ethereum-cryptography": "^2.2.1" } }, "sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog=="], + + "@ethereumjs/trie/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], + + "@ethereumjs/trie/lru-cache": ["lru-cache@10.1.0", "", {}, "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag=="], + + "@ethereumjs/tx/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/util/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@latticexyz/common/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/protocol-parser/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/schema-type/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/store/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/world/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@scure/bip32/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "@scure/bip32/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@scure/bip32/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], + + "@scure/bip39/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], + + "@tevm/base-bundler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/base-bundler/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + + "@tevm/bun-plugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + + "@tevm/bundler-cache/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/compiler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/config/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/resolutions/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/ts-plugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + + "@tevm/tsupconfig/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/unplugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + + "@zoltar/ui-core-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-zoltar/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-zoltar/@zoltar/ui-core-shared": ["@zoltar/ui-core-shared@file:../coreShared", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "ethereum-cryptography/@noble/curves": ["@noble/curves@1.9.0", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg=="], + + "ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "ox/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "prettier-plugin-solidity/@solidity-parser/parser": ["@solidity-parser/parser@0.17.0", "", {}, "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw=="], + + "prettier-plugin-solidity/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "solc-typed-ast/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "solc-typed-ast/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "solc-typed-ast/solc": ["solc@0.8.25", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-7P0TF8gPeudl1Ko3RGkyY6XVCxe2SdD/qQhtns1vl3yAbK/PDifKDLHGtx1t7mX3LgR7ojV7Fg/Kc6Q9D2T8UQ=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], + + "viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "viem/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "viem/abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], + + "web3-eth-abi/abitype": ["abitype@0.7.1", "", { "peerDependencies": { "typescript": ">=4.9.4", "zod": "^3 >=3.19.1" }, "optionalPeers": ["zod"] }, "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ=="], + + "web3-utils/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], + + "web3-validator/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], + + "web3-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@ethereumjs/trie/ethereum-cryptography/@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="], + + "@ethereumjs/trie/ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="], + + "@tevm/base-bundler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/base-bundler/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "@tevm/bun-plugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "@tevm/bundler-cache/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/compiler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/config/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/resolutions/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/ts-plugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "@tevm/tsupconfig/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/unplugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "@zoltar/ui-zoltar/@zoltar/ui-core-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "solc-typed-ast/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "solc-typed-ast/solc/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "web3-utils/ethereum-cryptography/@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="], + + "web3-utils/ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + + "web3-utils/ethereum-cryptography/@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="], + + "web3-utils/ethereum-cryptography/@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="], + + "web3-validator/ethereum-cryptography/@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="], + + "web3-validator/ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + + "web3-validator/ethereum-cryptography/@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="], + + "web3-validator/ethereum-cryptography/@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "web3-utils/ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "web3-utils/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "web3-validator/ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "web3-validator/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + } +} diff --git a/ui/statoblast/index.html b/ui/statoblast/index.html new file mode 100644 index 000000000..449c95fd0 --- /dev/null +++ b/ui/statoblast/index.html @@ -0,0 +1,117 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width" /> + <title>Augur Statoblast + + + + + + +
Loading...
+ + + + + diff --git a/ui/statoblast/package.json b/ui/statoblast/package.json new file mode 100644 index 000000000..cfe6d5452 --- /dev/null +++ b/ui/statoblast/package.json @@ -0,0 +1,49 @@ +{ + "description": "Augur Statoblast prediction-market operations interface", + "name": "@zoltar/ui-statoblast", + "private": true, + "repository": {}, + "license": "Unlicense", + "type": "module", + "devDependencies": { + "better-typescript-lib": "2.11.0", + "bun-types": "1.3.11", + "typescript": "5.8.2", + "funtypes": "5.1.1" + }, + "dependencies": { + "@preact/signals": "2.0.1", + "@noble/curves": "2.2.0", + "@noble/hashes": "2.2.0", + "@zoltar/shared": "file:../../shared", + "@zoltar/ui-core-shared": "file:../coreShared", + "@zoltar/ui-zoltar": "file:../zoltar", + "buffer": "6.0.3", + "micro-eth-signer": "0.18.1", + "micro-packed": "0.8.0", + "preact": "10.26.4", + "source-map": "0.6.1", + "tevm": "1.0.0-rc.151" + }, + "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", + "brace-expansion": "5.0.9", + "fast-uri": "3.1.5", + "nanoid": "3.3.18", + "postcss": "8.5.23", + "tmp": "0.2.7", + "ws": "8.21.0" + }, + "scripts": { + "generate": "bun ../coreShared/build/projectArtifacts.mts && bun ../coreShared/build/vendor.mts statoblast", + "vendor": "bun ../coreShared/build/vendor.mts statoblast", + "build:workers": "bun ../coreShared/build/workers.mts statoblast", + "build:tests": "bun ../coreShared/build/tests.mts statoblast", + "build": "bun run generate && bun x tsc --project tsconfig.json && bun run build:workers", + "build:prod": "bun run generate && bun ../coreShared/build/production.mts statoblast", + "watch": "bun ../coreShared/build/watch.mts statoblast", + "serve": "bun ../coreShared/dev-server.ts statoblast" + } +} diff --git a/ui/ts/app/App.tsx b/ui/statoblast/ts/app/App.tsx similarity index 70% rename from ui/ts/app/App.tsx rename to ui/statoblast/ts/app/App.tsx index e4beec23f..4bf84e931 100644 --- a/ui/ts/app/App.tsx +++ b/ui/statoblast/ts/app/App.tsx @@ -1,54 +1,62 @@ -import * as appCopy from '../copy/app.js' -import * as commonCopy from '../copy/common.js' -import * as marketCopy from '../copy/market.js' +import * as appCopy from '@zoltar/ui-core-shared/copy/app.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import { useSignal } from '@preact/signals' import type { ComponentChildren } from 'preact' import { useEffect, useRef, useState } from 'preact/hooks' import type { Address, Hash } from '@zoltar/shared/ethereum' -import { AppHeaderShell } from './components/AppHeaderShell.js' -import { AppPageHeading } from './components/AppPageHeading.js' +import { AppHeaderShell } from '@zoltar/ui-core-shared/app/components/AppHeaderShell.js' +import { AppPageHeading } from '@zoltar/ui-core-shared/app/components/AppPageHeading.js' +import { AppStatusNotices } from '@zoltar/ui-core-shared/app/components/AppStatusNotices.js' +import { GlobalTransactionTray } from '@zoltar/ui-core-shared/app/components/GlobalTransactionTray.js' +import { RouteSubNavigation } from '@zoltar/ui-core-shared/app/components/RouteSubNavigation.js' import { AppRouteContent } from './components/AppRouteContent.js' -import { AppStatusNotices } from './components/AppStatusNotices.js' -import { GlobalTransactionTray } from './components/GlobalTransactionTray.js' -import { GlobalTransactionPresentationProvider } from '../components/GlobalTransactionPresentationContext.js' -import { TransactionActionButtonLockProvider } from '../components/TransactionActionButton.js' -import { RouteSubNavigation } from './components/RouteSubNavigation.js' -import { useAppRouteEffects } from './hooks/useAppRouteEffects.js' -import { useDeploymentFlow } from '../features/deployment/hooks/useDeploymentFlow.js' -import { useForkAuctionOperations } from '../features/truth-auctions/hooks/useForkAuctionOperations.js' -import { useHashRoute } from './hooks/useHashRoute.js' +import { OverviewPanels } from '@zoltar/ui-zoltar/app/components/OverviewPanels.js' +import { useAppRouteEffects } from './useAppRouteEffects.js' +import { GlobalTransactionPresentationProvider } from '@zoltar/ui-core-shared/components/GlobalTransactionPresentationContext.js' +import { TransactionActionButtonLockProvider } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { useDeploymentFlow } from '@zoltar/ui-zoltar/features/deployment/hooks/useDeploymentFlow.js' +import { getDeploymentSections } from '@zoltar/ui-zoltar/features/deployment/lib/deployment.js' +import { formatUniverseCollectionLabel } from '@zoltar/ui-zoltar/features/universes/lib/universe.js' +import { useHashRoute } from '@zoltar/ui-core-shared/app/hooks/useHashRoute.js' import { useMarketCreation } from '../features/markets/hooks/useMarketCreation.js' -import { useOnchainState } from './hooks/useOnchainState.js' -import { useOpenOracleOperations } from '../features/open-oracle/hooks/useOpenOracleOperations.js' -import { usePriceOracleManager } from '../features/open-oracle/hooks/usePriceOracleManager.js' -import { useReportingOperations } from '../features/reporting/hooks/useReportingOperations.js' +import { useOnchainState } from '@zoltar/ui-core-shared/app/hooks/useOnchainState.js' +import { useOpenOracleOperations } from '@zoltar/ui-zoltar/features/open-oracle/hooks/useOpenOracleOperations.js' +import { usePriceOracleManager } from '@zoltar/ui-zoltar/features/open-oracle/hooks/usePriceOracleManager.js' +import { useRepPrices } from '@zoltar/ui-zoltar/features/open-oracle/hooks/useRepPrices.js' +import { useReportingOperations } from '@zoltar/ui-zoltar/features/reporting/hooks/useReportingOperations.js' +import { useForkAuctionOperations } from '../features/truth-auctions/hooks/useForkAuctionOperations.js' import { useSecurityPoolCreation } from '../features/security-pools/hooks/useSecurityPoolCreation.js' import { useSecurityPoolsOverview } from '../features/security-pools/hooks/useSecurityPoolsOverview.js' -import { useRepPrices } from '../features/open-oracle/hooks/useRepPrices.js' +import { createLoadSecurityVaultHandler } from '../features/security-pools/lib/securityVaultHandlers.js' import { useSecurityVaultOperations } from '../features/security-pools/hooks/useSecurityVaultOperations.js' import { useTradingOperations } from '../features/markets/hooks/useTradingOperations.js' -import { useUrlState } from './hooks/useUrlState.js' -import { getActiveSimulationController, initializeActiveEnvironment, shouldFollowWalletNetwork } from '../lib/activeEnvironment.js' -import { getAppPageTitle } from './lib/appPageTitle.js' -import { createSupportedNetworkChangeCoordinator } from './lib/supportedNetworkChange.js' -import { ChainBlockNumberContext, ChainTimestampContext } from '../lib/chainTimestamp.js' -import { getDeploymentSections } from '../features/deployment/lib/deployment.js' -import { resolveLoadableValueState } from '../lib/loadState.js' -import { getWalletScopedAccountAddress, isSupportedAppChain } from '../lib/network.js' -import { applyReportingFormUpdate } from '../features/reporting/lib/reportingForm.js' -import { createLoadSecurityVaultHandler } from '../features/security-pools/lib/securityVaultHandlers.js' -import { getUseQuestionForPoolHref, getUseQuestionForPoolState } from '../features/security-pools/lib/securityPoolNavigation.js' -import { createInitialTransactionTrayState, getTransactionActionLockReason, markTransactionCanceled, markTransactionFailed, markTransactionFinished, markTransactionPrepared, markTransactionPresented, markTransactionRequested, markTransactionSubmitted } from '../lib/transactionTray.js' -import type { TransactionTrayState } from '../lib/transactionTray.js' -import type { TransactionRequestPreview } from '../lib/chainBackend.js' -import { buildRouteHref, DEPLOY_ROUTE, getRouteHashSearch, OPEN_ORACLE_ROUTE, SECURITY_POOLS_ROUTE, ZOLTAR_ROUTE } from '../lib/routing.js' -import { writeOpenOracleViewQueryParam, writeSecurityPoolsViewQueryParam, writeZoltarViewQueryParam } from '../lib/urlParams.js' -import { getUniversePresentation } from '../lib/userCopy.js' -import { formatUniverseCollectionLabel } from '../features/universes/lib/universe.js' -import { resolveEnumValue, resolveFirstMatchingValue } from '../lib/viewState.js' -import type { ReportingFormState } from '../types/app.js' -import type { DeploymentRouteContentProps, MarketRouteContentProps, OpenOracleSectionProps, OpenOracleView, SecurityPoolsSectionProps, SecurityPoolsView, ZoltarView } from '../features/types.js' -import type { GlobalTransactionPresentation, TransactionIntent } from '../types/components.js' +import { useUrlState } from '@zoltar/ui-core-shared/app/hooks/useUrlState.js' +import { getActiveSimulationController, shouldFollowWalletNetwork } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { initializeStatoblastActiveEnvironment } from './activeEnvironment.js' +import { applicationTitle, formatAppDocumentTitle, getAppPageTitle } from './appPageTitle.js' +import { createSupportedNetworkChangeCoordinator } from '@zoltar/ui-core-shared/app/lib/supportedNetworkChange.js' +import { ChainBlockNumberContext, ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { getWalletScopedAccountAddress, isSupportedAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { applyReportingFormUpdate } from '@zoltar/ui-zoltar/features/reporting/lib/reportingForm.js' +import { createInitialTransactionTrayState, getTransactionActionLockReason, markTransactionCanceled, markTransactionFailed, markTransactionFinished, markTransactionPrepared, markTransactionPresented, markTransactionRequested, markTransactionSubmitted } from '@zoltar/ui-core-shared/lib/transactionTray.js' +import type { TransactionTrayState } from '@zoltar/ui-core-shared/lib/transactionTray.js' +import type { TransactionRequestPreview } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import { buildRouteHref, getRouteHashSearch } from '@zoltar/ui-core-shared/lib/routing.js' +import { writeOpenOracleViewQueryParam, writeSecurityPoolsViewQueryParam } from '@zoltar/ui-core-shared/lib/urlParams.js' +import { resolveEnumValue, resolveFirstMatchingValue } from '@zoltar/ui-core-shared/lib/viewState.js' +import { onchainStateDependencies } from './onchainStateDependencies.js' +import type { ReportingFormState } from '@zoltar/ui-zoltar/types/app.js' +import type { DeploymentRouteContentProps, OpenOracleSectionProps, OpenOracleView } from '@zoltar/ui-zoltar/features/types.js' +import type { Route } from '../types/app.js' +import type { SecurityPoolsSectionProps, SecurityPoolsView } from '../features/types.js' +import type { GlobalTransactionPresentation, RouteTabDefinition, TransactionIntent } from '@zoltar/ui-core-shared/types/components.js' + +const getRouteHashForRoute = (route: 'deploy' | 'open-oracle' | 'security-pools') => { + if (route === 'deploy') return '#/deploy' + if (route === 'open-oracle') return '#/open-oracle' + return '#/security-pools' +} + export function App() { const transactionState = useSignal(createInitialTransactionTrayState()) const deployNextMissingPending = useSignal(false) @@ -62,7 +70,7 @@ export function App() { getInFlightCount: () => transactionState.value.inFlightCount, replaceEnvironment: async canCommit => { let commitAllowed = false - await initializeActiveEnvironment(window.location, undefined, { + await initializeStatoblastActiveEnvironment(window.location, { shouldCommit: () => { commitAllowed = canCommit() return commitAllowed @@ -90,10 +98,7 @@ export function App() { setSecurityPoolAddress, setSecurityPoolQuestionId, setSelectedPoolView, - setZoltarView, - zoltarView, } = useUrlState() - const activeZoltarView = resolveEnumValue(zoltarView, 'questions', ['questions', 'create', 'fork', 'migrate']) const onTransactionRequested = (intent: TransactionIntent) => { transactionState.value = markTransactionRequested(transactionState.value, intent) } @@ -117,9 +122,10 @@ export function App() { void supportedNetworkChangeCoordinator.handleTransactionFinished() } const { navigate, route } = useHashRoute() + const activeRoute = resolveEnumValue(route, 'not-found', ['deploy', 'security-pools', 'open-oracle', 'not-found']) const { accountState, - augurStatoblastDeployed, + applicationDeploymentComplete, changeWallet, chainClockError, connectWallet, @@ -143,11 +149,14 @@ export function App() { disconnectWallet, switchNetwork, walletBootstrapComplete, - } = useOnchainState({ - activeEnvironmentNonce, - enableChainClock: route !== 'deploy', - ...(followSupportedWalletNetwork ? { onSupportedNetworkChange: () => void supportedNetworkChangeCoordinator.handleSupportedNetworkChange() } : {}), - }) + } = useOnchainState( + { + activeEnvironmentNonce, + enableChainClock: route !== 'deploy', + ...(followSupportedWalletNetwork ? { onSupportedNetworkChange: () => void supportedNetworkChangeCoordinator.handleSupportedNetworkChange() } : {}), + }, + onchainStateDependencies, + ) const readBackendReady = readBackendValidated && readBackendMessage === undefined const canReadOnchainData = environmentReady && readBackendReady && hasLoadedDeploymentStatuses const isOnActiveAppChain = isSupportedAppChain(accountState.chainId) @@ -168,56 +177,14 @@ export function App() { accountAddress: walletScopedAccountAddress, } const { busyStepId, deployNextMissing, deployStep, errorMessage: deploymentErrorMessage } = useDeploymentFlow({ ...baseHookConfig, deploymentStatuses, setDeploymentStatuses }) - const { - approveZoltarForkRep, - createChildUniverse: createZoltarChildUniverse, - createMarket, - forkZoltar, - hasLoadedZoltarQuestions, - loadZoltarForkAccess, - loadingZoltarForkAccess, - loadingZoltarQuestionCount, - loadingZoltarQuestion, - loadingZoltarQuestions, - loadingZoltarUniverse, - loadZoltarQuestionPage, - loadZoltarQuestion, - loadZoltarQuestions, - loadZoltarUniverse, - marketCreating, - marketError, - marketForm, - marketResult, - migrateInternalRep, - prepareRepForMigration, - resetMarket, - setMarketForm, - setZoltarForkQuestionId, - setZoltarMigrationForm, - zoltarChildUniverseError, - zoltarChildUniversePendingOutcomeIndex, - zoltarForkApproval, - zoltarForkActiveAction, - zoltarForkError, - zoltarForkPending, - zoltarForkQuestionId, - zoltarForkRepBalanceAttoRep, - zoltarMigrationChildRepBalancesAttoRep, - zoltarMigrationActiveAction, - zoltarMigrationError, - zoltarMigrationForm, - zoltarMigrationPending, - zoltarMigrationPreparedRepBalanceAttoRep, - zoltarQuestionCount, - zoltarQuestionLookupError, - zoltarQuestionLookupId, - zoltarQuestionPage, - zoltarQuestions, - zoltarQuestionsError, - zoltarUniverse, - zoltarUniverseError, - zoltarUniverseMissing, - } = useMarketCreation({ ...walletScopedHookConfig, activeUniverseId, activeZoltarView, autoLoadInitialData: walletBootstrapComplete && canReadOnchainData, deploymentStatuses, environmentRefreshKey: activeEnvironmentNonce }) + const { hasLoadedZoltarQuestions, loadZoltarForkAccess, loadingZoltarForkAccess, loadingZoltarQuestions, loadZoltarQuestions, zoltarQuestions, zoltarUniverse, zoltarUniverseError } = useMarketCreation({ + ...walletScopedHookConfig, + activeUniverseId, + activeZoltarView: 'questions', + autoLoadInitialData: walletBootstrapComplete && canReadOnchainData, + deploymentStatuses, + environmentRefreshKey: activeEnvironmentNonce, + }) const zoltarUniverseHasForked = zoltarUniverse?.hasForked === true const { checkingDuplicateOriginPool, createPool, duplicateOriginPoolExists, loadingMarketDetails, marketDetails, poolCreationMarketDetails, resetSecurityPoolCreation, securityPoolCreating, securityPoolError, securityPoolForm, securityPoolResult, setSecurityPoolForm } = useSecurityPoolCreation({ ...walletScopedHookConfig, @@ -284,7 +251,7 @@ export function App() { }) const { loadingReportingDetails, loadReporting, onReportOutcome, reportingActiveAction, reportingDetails, reportingError, reportingForm, reportingResult, setReportingForm, withdrawEscalation } = useReportingOperations({ ...walletScopedHookConfig, selectedSecurityPoolAddress: securityPoolAddress }) const updateReportingForm = (update: Partial) => { - setReportingForm(current => applyReportingFormUpdate(current, update)) + setReportingForm((current: ReportingFormState) => applyReportingFormUpdate(current, update)) } const { checkedSecurityPoolAddress, @@ -329,9 +296,6 @@ export function App() { setLiquidationReceiverVault, setLiquidationApprovalId, setLiquidationTimeoutMinutes, - securityPoolsLoadedEnvironmentRefreshKey, - securityPoolsLoadError, - securityPoolsLoadErrorEnvironmentRefreshKey, } = useSecurityPoolsOverview({ ...walletScopedHookConfig, environmentRefreshKey: activeEnvironmentNonce }) const { createCompleteSet, loadingTradingDetails, loadingTradingForkUniverse, migrateShares, redeemCompleteSet, redeemShares, setTradingForm, tradingActiveAction, tradingDetails, tradingError, tradingForm, tradingForkUniverse, tradingResult } = useTradingOperations({ ...walletScopedHookConfig, @@ -370,7 +334,7 @@ export function App() { refreshRepPrices() } const refreshActiveEnvironment = async () => { - await initializeActiveEnvironment() + await initializeStatoblastActiveEnvironment() setActiveEnvironmentNonce(currentNonce => currentNonce + 1) setSelectedPoolRefreshNonce(currentNonce => currentNonce + 1) await refreshSimulationView() @@ -379,20 +343,12 @@ export function App() { const lastStagedVaultRepRefreshHash = useRef(undefined) const deploymentSections = getDeploymentSections(deploymentStatuses) const errorMessages = [deploymentErrorMessage, ...onchainErrorMessages.filter(message => message !== deploymentStatusError), chainClockError].filter((message): message is string => message !== undefined) - const augurStatoblastDeploymentMissing = canReadOnchainData && augurStatoblastDeployed === false - const showDeployTab = deploymentStatusError !== undefined || augurStatoblastDeploymentMissing || (hasLoadedDeploymentStatuses && deploymentStatuses.some(step => !step.deployed)) - const showAugurStatoblastDeploymentWarning = augurStatoblastDeploymentMissing - const zoltarUniverseState = resolveLoadableValueState({ - isLoading: loadingZoltarUniverse, - isMissing: zoltarUniverseMissing, - value: zoltarUniverse, - }) - const showZoltarUniverseWarning = canReadOnchainData && zoltarUniverseState === 'missing' - const disableRouteContent = route !== 'deploy' && (!readBackendReady || augurStatoblastDeploymentMissing || showZoltarUniverseWarning) + const applicationDeploymentMissing = canReadOnchainData && applicationDeploymentComplete === false + const showApplicationDeploymentWarning = applicationDeploymentMissing + const disableRouteContent = route !== 'deploy' && (!readBackendReady || applicationDeploymentMissing) const isRouteContentDisabled = disableRouteContent - const universeLabel = formatUniverseCollectionLabel([activeUniverseId]) - const universePresentation = showZoltarUniverseWarning ? getUniversePresentation(zoltarUniverseState) : undefined const overviewProps = { + activeUniverseId, accountState, isConnectingWallet, isManagingWallet, @@ -405,6 +361,7 @@ export function App() { onGoToGenesisUniverse: () => setActiveUniverseId(0n), onRefreshRepPrices: refreshRepPrices, onSwitchNetwork: () => void switchNetwork(), + parentUniverseId: zoltarUniverse?.parentUniverseId, repPerEthFailure, repPerEthPrice, repPerEthSource, @@ -416,20 +373,20 @@ export function App() { readBackendStatus, universeForkTime: zoltarUniverse?.forkTime, universeHasForked: zoltarUniverse?.hasForked, - universePresentation, - universeLabel, - universeRepBalanceAttoRep: zoltarForkRepBalanceAttoRep, + universePresentation: undefined, + universeLabel: formatUniverseCollectionLabel([activeUniverseId]), + universeRepBalanceAttoRep: zoltarUniverse?.totalTheoreticalSupplyAttoRep, isRefreshing, walletBootstrapComplete, } + const tabs: RouteTabDefinition[] = [ + { hash: getRouteHashForRoute('security-pools'), label: commonCopy.securityPools, route: 'security-pools' }, + { hash: getRouteHashForRoute('open-oracle'), label: appCopy.oracleReports, route: 'open-oracle' }, + { hash: getRouteHashForRoute('deploy'), label: commonCopy.deploy, route: 'deploy' }, + ] const tabNavigationProps = { route, - showDeployTab, - augurStatoblastDeployed: hasLoadedDeploymentStatuses && augurStatoblastDeployed === true && !showZoltarUniverseWarning, - deployRoute: DEPLOY_ROUTE, - marketRoute: ZOLTAR_ROUTE, - openOracleRoute: OPEN_ORACLE_ROUTE, - securityPoolsRoute: SECURITY_POOLS_ROUTE, + tabs, onRouteChange: navigate, } const selectedPool = securityPools.find(pool => pool.securityPoolAddress.toLowerCase() === securityPoolAddress.toLowerCase()) @@ -443,7 +400,7 @@ export function App() { const activeSecurityPoolsView = resolveEnumValue(securityPoolsView, derivedSecurityPoolsView, ['browse', 'create', 'operate']) const derivedOpenOracleView = resolveFirstMatchingValue([[urlOpenOracleReportId !== '' || openOracleForm.reportId !== '', 'selected-report']], 'browse') const activeOpenOracleView = resolveEnumValue(openOracleView, derivedOpenOracleView, ['browse', 'create', 'selected-report']) - const pageTitle = getAppPageTitle({ activeOpenOracleView, activeSecurityPoolsView, activeZoltarView, route }) + const pageTitle = getAppPageTitle({ activeOpenOracleView, activeSecurityPoolsView, route: activeRoute }) const refreshSelectedPoolData = (requestedSecurityPoolAddress?: string) => { const nextSecurityPoolAddress = requestedSecurityPoolAddress ?? securityPoolAddress if (!walletBootstrapComplete) return @@ -460,15 +417,6 @@ export function App() { deployNextMissingPending.value = false } } - const onUseQuestionForPool = (questionId: string) => { - const { marketId } = getUseQuestionForPoolState(questionId) - resetSecurityPoolCreation() - setSecurityPoolForm(current => ({ - ...current, - marketId, - })) - window.location.hash = getUseQuestionForPoolHref(questionId, activeUniverseId) - } useEffect(() => { const securityVaultRepRefreshHash = securityVaultResult?.action === 'depositRepToVault' || securityVaultResult?.action === 'redeemRepFromVault' || (securityVaultResult?.action === 'queueWithdrawRep' && securityVaultResult.stagedExecution?.success === true) ? securityVaultResult.hash : undefined if (securityVaultRepRefreshHash === undefined) { @@ -491,15 +439,14 @@ export function App() { }, [loadZoltarForkAccess, poolPriceOracleResult]) useAppRouteEffects({ accountAddress: walletScopedAccountAddress, - activeZoltarView, - augurStatoblastDeploymentMissing, + applicationDeploymentMissing, environmentReady: canReadOnchainData, activeEnvironmentNonce, loadOracleReport: async reportId => await loadOracleReport(reportId), loadSecurityPools: async requestedSecurityPoolAddress => await loadSecurityPools(requestedSecurityPoolAddress), navigate, resetSecurityPoolCreation, - route, + route: activeRoute, securityPoolAddress, securityPoolQuestionId, securityPoolResultHash: securityPoolResult?.deployPoolHash, @@ -525,69 +472,11 @@ export function App() { deploymentStatuses, isLoadingDeploymentStatuses, isOnActiveAppChain, + deploymentCompleteHref: buildRouteHref(getRouteHashForRoute('security-pools'), writeSecurityPoolsViewQueryParam(getRouteHashSearch(), 'browse')), onDeploy: deployStep, onDeployNextMissing: () => void onDeployNextMissing(), onRetryDeploymentStatus: () => void refreshState({ loadChainClock: false, loadWalletState: false }), } - const marketRouteContentProps: MarketRouteContentProps = { - accountState, - activeUniverseId, - activeView: activeZoltarView, - securityPools: securityPoolsLoadedEnvironmentRefreshKey === activeEnvironmentNonce ? securityPools : [], - environmentRefreshKey: activeEnvironmentNonce, - hasLoadedSecurityPools: securityPoolsLoadedEnvironmentRefreshKey === activeEnvironmentNonce, - loadingSecurityPools, - onLoadSecurityPools: () => void loadSecurityPools(), - securityPoolsLoadError: securityPoolsLoadErrorEnvironmentRefreshKey === activeEnvironmentNonce ? securityPoolsLoadError : undefined, - hasLoadedZoltarQuestions, - loadingZoltarForkAccess, - zoltarForkActiveAction, - loadingZoltarQuestionCount, - loadingZoltarQuestion, - loadingZoltarQuestions, - loadingZoltarUniverse, - zoltarUniverseState, - onCreateChildUniverseForOutcomeIndex: (outcomeIndex: bigint) => void createZoltarChildUniverse(outcomeIndex), - marketForm, - marketCreating, - marketError, - marketResult, - onActiveViewChange: view => setZoltarView(view), - onApproveZoltarForkRep: amount => void approveZoltarForkRep(amount), - onCreateMarket: () => void createMarket(), - onForkZoltar: () => void forkZoltar(), - onLoadZoltarQuestions: loadZoltarQuestions, - onLoadZoltarQuestion: loadZoltarQuestion, - onLoadZoltarQuestionPage: loadZoltarQuestionPage, - onMigrateInternalRep: () => void migrateInternalRep(), - onMarketFormChange: update => setMarketForm(current => ({ ...current, ...update })), - onPrepareRepForMigration: () => void prepareRepForMigration(), - onResetMarket: resetMarket, - onUseQuestionForFork: (questionId: string) => setZoltarForkQuestionId(questionId), - onUseQuestionForPool, - onZoltarMigrationFormChange: update => setZoltarMigrationForm(current => ({ ...current, ...update })), - zoltarQuestionCount, - zoltarQuestionLookupError, - zoltarQuestionLookupId, - zoltarQuestionPage, - zoltarForkApproval, - zoltarForkError, - zoltarChildUniverseError, - zoltarChildUniversePendingOutcomeIndex, - zoltarForkPending, - zoltarForkQuestionId, - zoltarForkRepBalanceAttoRep, - zoltarMigrationError, - zoltarMigrationForm, - zoltarMigrationChildRepBalancesAttoRep, - zoltarMigrationActiveAction, - zoltarMigrationPending, - zoltarMigrationPreparedRepBalanceAttoRep, - zoltarQuestions, - zoltarQuestionsError, - zoltarUniverse, - onZoltarForkQuestionIdChange: (questionId: string) => setZoltarForkQuestionId(questionId), - } const securityPoolsRouteContentProps: SecurityPoolsSectionProps = { activeView: activeSecurityPoolsView, onActiveUniverseChange: setActiveUniverseId, @@ -799,33 +688,27 @@ export function App() { }, } const openOracleRouteContentProps: OpenOracleSectionProps = { - activeView: activeOpenOracleView, accountState, + activeView: activeOpenOracleView, environmentReady: canReadOnchainData, environmentRefreshKey: activeEnvironmentNonce, + loadingOpenOracleCreate, + onActiveViewChange: view => setOpenOracleView(view), onApproveToken1: amount => void approveToken1(amount), onApproveToken2: amount => void approveToken2(amount), - onCancelOpenOracleWithdrawalBalanceCheck: cancelWithdrawalBalanceCheck, + onCancelOpenOracleWithdrawalBalanceCheck: () => cancelWithdrawalBalanceCheck(), onCreateOpenOracleGame: () => void createOpenOracleGame(), onDisputeReport: () => void disputeReport(), - onLoadOracleReport: reportId => { - if (reportId === undefined) return - void loadOracleReport(reportId) - }, - onActiveViewChange: view => setOpenOracleView(view), + onLoadOracleReport: reportId => void loadOracleReport(reportId), + onOpenOracleFormChange: update => setOpenOracleForm(current => ({ ...current, ...update })), onOpenOracleCreateFormChange: update => setOpenOracleCreateForm(current => ({ ...current, ...update })), - onOpenOracleFormChange: update => { - setOpenOracleForm(current => ({ ...current, ...update })) - if (update.reportId !== undefined) setOpenOracleReport(update.reportId) - }, onSettleReport: () => void settleReport(), onWithdrawOpenOracleBalance: (balance, reviewedAmount) => void withdrawBalance(balance, reviewedAmount), - loadingOpenOracleCreate, openOracleActiveAction, openOracleActiveWithdrawalBalance, - openOracleError, openOracleCreateForm, openOracleCreateFieldErrors, + openOracleError, openOracleDisputeSubmission, openOracleForm, openOracleReportLookupState, @@ -839,35 +722,16 @@ export function App() { openOracleWithdrawableBalancesLoading, } let routeSubNavigation: ComponentChildren = undefined - if (route === 'zoltar') { - routeSubNavigation = ( - setZoltarView(view)} - options={[ - { href: buildRouteHref(ZOLTAR_ROUTE, writeZoltarViewQueryParam(getRouteHashSearch(), 'questions')), label: marketCopy.browseQuestions, value: 'questions' }, - { href: buildRouteHref(ZOLTAR_ROUTE, writeZoltarViewQueryParam(getRouteHashSearch(), 'create')), label: commonCopy.createQuestion, value: 'create' }, - { href: buildRouteHref(ZOLTAR_ROUTE, writeZoltarViewQueryParam(getRouteHashSearch(), 'fork')), label: marketCopy.forkUniverse, value: 'fork' }, - { - label: marketCopy.repMigration, - value: 'migrate', - disabled: zoltarUniverse?.hasForked !== true, - ...(zoltarUniverse?.hasForked === true ? { href: buildRouteHref(ZOLTAR_ROUTE, writeZoltarViewQueryParam(getRouteHashSearch(), 'migrate')) } : {}), - }, - ]} - /> - ) - } else if (route === 'security-pools') { + if (route === 'security-pools') { routeSubNavigation = ( setSecurityPoolsView(view)} options={[ - { href: buildRouteHref(SECURITY_POOLS_ROUTE, writeSecurityPoolsViewQueryParam(getRouteHashSearch(), 'browse')), label: commonCopy.browsePools, value: 'browse' }, - { href: buildRouteHref(SECURITY_POOLS_ROUTE, writeSecurityPoolsViewQueryParam(getRouteHashSearch(), 'create')), label: commonCopy.createPool, value: 'create' }, - { href: buildRouteHref(SECURITY_POOLS_ROUTE, writeSecurityPoolsViewQueryParam(getRouteHashSearch(), 'operate')), label: commonCopy.managePool, value: 'operate' }, + { href: buildRouteHref(getRouteHashForRoute('security-pools'), writeSecurityPoolsViewQueryParam(getRouteHashSearch(), 'browse')), label: commonCopy.browsePools, value: 'browse' }, + { href: buildRouteHref(getRouteHashForRoute('security-pools'), writeSecurityPoolsViewQueryParam(getRouteHashSearch(), 'create')), label: commonCopy.createPool, value: 'create' }, + { href: buildRouteHref(getRouteHashForRoute('security-pools'), writeSecurityPoolsViewQueryParam(getRouteHashSearch(), 'operate')), label: commonCopy.managePool, value: 'operate' }, ]} /> ) @@ -878,15 +742,14 @@ export function App() { value={activeOpenOracleView} onChange={view => setOpenOracleView(view)} options={[ - { href: buildRouteHref(OPEN_ORACLE_ROUTE, writeOpenOracleViewQueryParam(getRouteHashSearch(), 'browse')), label: appCopy.browseReports, value: 'browse' }, - { href: buildRouteHref(OPEN_ORACLE_ROUTE, writeOpenOracleViewQueryParam(getRouteHashSearch(), 'create')), label: appCopy.createReport, value: 'create' }, - { href: buildRouteHref(OPEN_ORACLE_ROUTE, writeOpenOracleViewQueryParam(getRouteHashSearch(), 'selected-report')), label: appCopy.viewReport, value: 'selected-report' }, + { href: buildRouteHref(getRouteHashForRoute('open-oracle'), writeOpenOracleViewQueryParam(getRouteHashSearch(), 'browse')), label: appCopy.browseReports, value: 'browse' }, + { href: buildRouteHref(getRouteHashForRoute('open-oracle'), writeOpenOracleViewQueryParam(getRouteHashSearch(), 'create')), label: appCopy.createReport, value: 'create' }, + { href: buildRouteHref(getRouteHashForRoute('open-oracle'), writeOpenOracleViewQueryParam(getRouteHashSearch(), 'selected-report')), label: appCopy.viewReport, value: 'selected-report' }, ]} /> ) } const transactionRouteKey = (() => { - if (route === 'zoltar') return `${route}:${activeZoltarView}` if (route === 'security-pools') return `${route}:${activeSecurityPoolsView}` if (route === 'open-oracle') return `${route}:${activeOpenOracleView}` return route @@ -896,25 +759,16 @@ export function App() {
- - void loadZoltarUniverse({ clearCurrentState: false })} - readBackendMessage={readBackendMessage} - readBackendStatus={readBackendStatus} - simulationBootstrapError={environmentBootstrapError} - showAugurStatoblastDeploymentWarning={showAugurStatoblastDeploymentWarning} - zoltarUniverseError={zoltarUniverseError} - /> - + + + } simulationController={simulationController} subNavigation={routeSubNavigation} tabNavigation={tabNavigationProps} onEnvironmentChanged={refreshActiveEnvironment} onRefresh={refreshSimulationView} />
- +
diff --git a/ui/statoblast/ts/app/activeEnvironment.ts b/ui/statoblast/ts/app/activeEnvironment.ts new file mode 100644 index 000000000..4914070ee --- /dev/null +++ b/ui/statoblast/ts/app/activeEnvironment.ts @@ -0,0 +1,9 @@ +import { initializeActiveEnvironment } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' + +export const statoblastActiveEnvironmentDependencies = { + appId: 'statoblast', +} satisfies NonNullable[1]> + +export function initializeStatoblastActiveEnvironment(location: Parameters[0] = window.location, options: Parameters[2] = {}) { + return initializeActiveEnvironment(location, statoblastActiveEnvironmentDependencies, options) +} diff --git a/ui/statoblast/ts/app/appPageTitle.ts b/ui/statoblast/ts/app/appPageTitle.ts new file mode 100644 index 000000000..65ab2c169 --- /dev/null +++ b/ui/statoblast/ts/app/appPageTitle.ts @@ -0,0 +1,32 @@ +import * as appCopy from '@zoltar/ui-core-shared/copy/app.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import type { Route } from '../types/app.js' +import type { OpenOracleView } from '@zoltar/ui-zoltar/features/types.js' +import type { SecurityPoolsView } from '../features/types.js' + +export type AppPageTitleInput = { + activeOpenOracleView: OpenOracleView + activeSecurityPoolsView: SecurityPoolsView + route: Route +} + +export function getAppPageTitle({ activeOpenOracleView, activeSecurityPoolsView, route }: AppPageTitleInput) { + if (route === 'deploy') return appCopy.deployContracts + if (route === 'security-pools') { + if (activeSecurityPoolsView === 'create') return commonCopy.createSecurityPool + if (activeSecurityPoolsView === 'operate') return appCopy.manageSecurityPool + return commonCopy.securityPools + } + if (route === 'open-oracle') { + if (activeOpenOracleView === 'create') return appCopy.createOracleReport + if (activeOpenOracleView === 'selected-report') return appCopy.oracleReportDetails + return appCopy.oracleReports + } + return appCopy.pageNotFoundTitle +} + +export const applicationTitle = 'Augur Statoblast' + +export function formatAppDocumentTitle(pageTitle: string) { + return `${pageTitle} | ${applicationTitle}` +} diff --git a/ui/statoblast/ts/app/components/AppRouteContent.tsx b/ui/statoblast/ts/app/components/AppRouteContent.tsx new file mode 100644 index 000000000..020de0387 --- /dev/null +++ b/ui/statoblast/ts/app/components/AppRouteContent.tsx @@ -0,0 +1,36 @@ +import type { ComponentProps } from 'preact' +import { DeploymentRouteContent } from '@zoltar/ui-zoltar/features/deployment/components/DeploymentRouteContent.js' +import { NotFoundSection } from '@zoltar/ui-core-shared/app/components/NotFoundSection.js' +import { OpenOracleSection } from '@zoltar/ui-zoltar/features/open-oracle/components/OpenOracleSection.js' +import { SecurityPoolsSection } from '../../features/security-pools/components/SecurityPoolsSection.js' +import type { Route } from '../../types/app.js' +import { shouldRenderAppRouteContent } from '@zoltar/ui-core-shared/app/lib/appRouteGate.js' + +type Props = { + deploy: ComponentProps + openOracle: ComponentProps + readBackendMessage: string | undefined + route: Route + securityPools: ComponentProps +} + +function shouldRenderRouteContent({ readBackendMessage, route }: Pick) { + return shouldRenderAppRouteContent(route, readBackendMessage) +} + +export function AppRouteContent({ deploy, openOracle, readBackendMessage, route, securityPools }: Props) { + if (!shouldRenderRouteContent({ readBackendMessage, route })) return null + + switch (route) { + case 'deploy': + return + case 'security-pools': + return + case 'open-oracle': + return + case 'not-found': + return + default: + return + } +} diff --git a/ui/statoblast/ts/app/onchainStateDependencies.ts b/ui/statoblast/ts/app/onchainStateDependencies.ts new file mode 100644 index 000000000..c8e94eeb4 --- /dev/null +++ b/ui/statoblast/ts/app/onchainStateDependencies.ts @@ -0,0 +1,4 @@ +import { getDeploymentSteps, loadDeploymentStatusOracleSnapshot, loadErc20Balance } from '../protocol/index.js' +import { getWethAddress } from '@zoltar/ui-zoltar/protocol/uniswapQuoter.js' + +export const onchainStateDependencies = { getDeploymentSteps, getWethAddress, loadDeploymentStatusOracleSnapshot, loadErc20Balance } diff --git a/ui/ts/app/hooks/useAppRouteEffects.ts b/ui/statoblast/ts/app/useAppRouteEffects.ts similarity index 79% rename from ui/ts/app/hooks/useAppRouteEffects.ts rename to ui/statoblast/ts/app/useAppRouteEffects.ts index 252b00da6..95f72dcfe 100644 --- a/ui/ts/app/hooks/useAppRouteEffects.ts +++ b/ui/statoblast/ts/app/useAppRouteEffects.ts @@ -1,20 +1,18 @@ import { useEffect, useRef } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { normalizeAddress } from '../../lib/address.js' - -type AppRoute = 'deploy' | 'not-found' | 'open-oracle' | 'security-pools' | 'zoltar' +import { normalizeAddress } from '@zoltar/ui-core-shared/lib/address.js' +import type { Route } from '../types/app.js' type Props = { accountAddress: Address | undefined - activeZoltarView: 'create' | 'fork' | 'migrate' | 'questions' - augurStatoblastDeploymentMissing: boolean + applicationDeploymentMissing: boolean activeEnvironmentNonce: number environmentReady: boolean loadOracleReport: (reportId: string) => Promise loadSecurityPools: (securityPoolAddress?: string) => Promise - navigate: (route: 'deploy' | 'open-oracle' | 'security-pools' | 'zoltar') => void + navigate: (route: 'deploy' | 'open-oracle' | 'security-pools') => void resetSecurityPoolCreation: () => void - route: AppRoute + route: Route securityPoolAddress: string securityPoolQuestionId: string securityPoolResultHash: string | undefined @@ -31,35 +29,19 @@ type Props = { walletBootstrapComplete: boolean } -export function shouldLoadOpenOracleReportFromUrl({ environmentReady, route, urlOpenOracleReportId }: { environmentReady: boolean; route: AppRoute; urlOpenOracleReportId: string }) { +export function shouldLoadOpenOracleReportFromUrl({ environmentReady, route, urlOpenOracleReportId }: { environmentReady: boolean; route: Route; urlOpenOracleReportId: string }) { return environmentReady && route === 'open-oracle' && urlOpenOracleReportId !== '' } -export function shouldLoadMarketSecurityPools({ activeZoltarView, environmentReady, route, walletBootstrapComplete }: { activeZoltarView: Props['activeZoltarView']; environmentReady: boolean; route: AppRoute; walletBootstrapComplete: boolean }) { - return environmentReady && walletBootstrapComplete && route === 'zoltar' && activeZoltarView === 'questions' -} - -export function shouldRefreshSelectedPoolForRoute({ - environmentReady, - route, - securityPoolAddress, - selectedPoolSecurityPoolAddress, - walletBootstrapComplete, -}: { - environmentReady: boolean - route: AppRoute - securityPoolAddress: string - selectedPoolSecurityPoolAddress: string | undefined - walletBootstrapComplete: boolean -}) { +export function shouldRefreshSelectedPoolForRoute({ environmentReady, route, securityPoolAddress, selectedPoolSecurityPoolAddress, walletBootstrapComplete }: { environmentReady: boolean; route: Route; securityPoolAddress: string; selectedPoolSecurityPoolAddress: string | undefined; walletBootstrapComplete: boolean }) { return environmentReady && route === 'security-pools' && walletBootstrapComplete && securityPoolAddress !== '' && selectedPoolSecurityPoolAddress === undefined } -export function shouldSyncSecurityPoolAddressToRouteForms({ route }: { route: AppRoute; securityPoolAddress: string }) { +export function shouldSyncSecurityPoolAddressToRouteForms({ route }: { route: Route; securityPoolAddress: string }) { return route === 'security-pools' } -export function getSelectedVaultOwnerForRoutePoolChange({ accountAddress, lastSecurityPoolAddress, route, securityPoolAddress }: { accountAddress: Address | undefined; lastSecurityPoolAddress: string | undefined; route: AppRoute; securityPoolAddress: string }) { +export function getSelectedVaultOwnerForRoutePoolChange({ accountAddress, lastSecurityPoolAddress, route, securityPoolAddress }: { accountAddress: Address | undefined; lastSecurityPoolAddress: string | undefined; route: Route; securityPoolAddress: string }) { if (route !== 'security-pools') return undefined const normalizedSecurityPoolAddress = normalizeAddress(securityPoolAddress) ?? '' const normalizedLastSecurityPoolAddress = normalizeAddress(lastSecurityPoolAddress) @@ -70,8 +52,7 @@ export function getSelectedVaultOwnerForRoutePoolChange({ accountAddress, lastSe export function useAppRouteEffects({ accountAddress, - activeZoltarView, - augurStatoblastDeploymentMissing, + applicationDeploymentMissing, activeEnvironmentNonce, environmentReady, loadOracleReport, @@ -98,7 +79,6 @@ export function useAppRouteEffects({ const loadSecurityPoolsRef = useRef(loadSecurityPools) const navigateRef = useRef(navigate) const lastRequestedOpenOracleReportId = useRef(undefined) - const lastRequestedMarketSecurityPools = useRef(undefined) const lastRequestedSecurityPoolAddress = useRef(undefined) const lastSelectedPoolEnvironmentNonce = useRef(undefined) const lastSelectedSecurityPoolAddress = useRef(undefined) @@ -109,19 +89,6 @@ export function useAppRouteEffects({ loadSecurityPoolsRef.current = loadSecurityPools navigateRef.current = navigate - useEffect(() => { - if (!shouldLoadMarketSecurityPools({ activeZoltarView, environmentReady, route, walletBootstrapComplete })) { - lastRequestedMarketSecurityPools.current = undefined - return - } - const requestKey = `${activeEnvironmentNonce}:${accountAddress ?? ''}` - if (lastRequestedMarketSecurityPools.current === requestKey) return - lastRequestedMarketSecurityPools.current = requestKey - void loadSecurityPoolsRef.current().then(loaded => { - if (loaded === false && lastRequestedMarketSecurityPools.current === requestKey) lastRequestedMarketSecurityPools.current = undefined - }) - }, [accountAddress, activeEnvironmentNonce, activeZoltarView, environmentReady, route, walletBootstrapComplete]) - useEffect(() => { if (route !== 'open-oracle') { lastSyncedOpenOracleReportId.current = undefined @@ -219,8 +186,8 @@ export function useAppRouteEffects({ }, [environmentReady, route, securityPoolAddress, tradingResultHash]) useEffect(() => { - if (!augurStatoblastDeploymentMissing) return + if (!applicationDeploymentMissing) return if (route === 'deploy') return navigateRef.current('deploy') - }, [augurStatoblastDeploymentMissing, route]) + }, [applicationDeploymentMissing, route]) } diff --git a/ui/ts/features/markets/components/MarketCreateQuestionSection.tsx b/ui/statoblast/ts/features/markets/components/MarketCreateQuestionSection.tsx similarity index 91% rename from ui/ts/features/markets/components/MarketCreateQuestionSection.tsx rename to ui/statoblast/ts/features/markets/components/MarketCreateQuestionSection.tsx index 6c79c1a08..c96e70997 100644 --- a/ui/ts/features/markets/components/MarketCreateQuestionSection.tsx +++ b/ui/statoblast/ts/features/markets/components/MarketCreateQuestionSection.tsx @@ -1,33 +1,33 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as marketCopy from '@zoltar/ui-zoltar/copy/market.js' import { useEffect, useMemo, useState } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { EnumDropdown, type EnumDropdownOption } from '../../../components/EnumDropdown.js' -import { EntityCard } from '../../../components/EntityCard.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { OutcomeChipRow } from './OutcomeChipRow.js' -import { Question, getQuestionTitle } from './Question.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionNetworkValue } from '../../../components/TransactionNetworkValue.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { TransactionHashLink } from '../../../components/TransactionHashLink.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { MetricField } from '../../../components/MetricField.js' -import { assertNever } from '../../../lib/assert.js' +import { EnumDropdown, type EnumDropdownOption } from '@zoltar/ui-core-shared/components/EnumDropdown.js' +import { EntityCard } from '@zoltar/ui-core-shared/components/EntityCard.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { OutcomeChipRow } from '@zoltar/ui-core-shared/components/OutcomeChipRow.js' +import { Question, getQuestionTitle } from '@zoltar/ui-core-shared/components/Question.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionNetworkValue } from '@zoltar/ui-core-shared/components/TransactionNetworkValue.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { TransactionHashLink } from '@zoltar/ui-core-shared/components/TransactionHashLink.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' import { getMarketCreationOutcomeLabels, hasMarketEndTimePassed, validateMarketForm } from '../lib/marketCreation.js' -import { useChainTimestamp } from '../../../lib/chainTimestamp.js' -import { appendInvalidOutcomeLabelIfMissing, isInvalidOutcomeLabel } from '../lib/outcomeLabels.js' -import { clampScalarTickIndex, parseScalarFormInputs } from '../lib/scalarOutcome.js' -import { getMarketTypeLabel } from '../lib/marketType.js' +import { useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { appendInvalidOutcomeLabelIfMissing, isInvalidOutcomeLabel } from '@zoltar/ui-core-shared/lib/outcomeLabels.js' +import { clampScalarTickIndex, parseScalarFormInputs } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' +import { getMarketTypeLabel } from '@zoltar/ui-core-shared/lib/marketType.js' import type { MarketFormState } from '../../../types/app.js' -import type { MarketCreationResult, MarketDetails } from '../../../types/contracts.js' +import type { MarketCreationResult, MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' import { ScalarCreatePreview, type ScalarCreatePreviewDetails } from './ScalarCreatePreview.js' -import { getWrongNetworkMessage } from '../../../lib/network.js' -import * as transactionReviewCopy from '../../../copy/transactionReview.js' -import { TimestampValue } from '../../../components/TimestampValue.js' -import { tryParseTimestampInput } from '../lib/marketForm.js' +import { getWrongNetworkMessage } from '@zoltar/ui-core-shared/lib/network.js' +import { tryParseTimestampInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import * as transactionReviewCopy from '@zoltar/ui-core-shared/copy/transactionReview.js' const MARKET_TYPE_OPTIONS: EnumDropdownOption[] = [ { value: 'binary', label: marketCopy.binary }, diff --git a/ui/ts/features/markets/components/MarketOverviewSection.tsx b/ui/statoblast/ts/features/markets/components/MarketOverviewSection.tsx similarity index 84% rename from ui/ts/features/markets/components/MarketOverviewSection.tsx rename to ui/statoblast/ts/features/markets/components/MarketOverviewSection.tsx index c538df549..05321dd6b 100644 --- a/ui/ts/features/markets/components/MarketOverviewSection.tsx +++ b/ui/statoblast/ts/features/markets/components/MarketOverviewSection.tsx @@ -1,24 +1,24 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as marketCopy from '@zoltar/ui-zoltar/copy/market.js' import { useState } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { ChildUniverseDeploymentModal } from '../../universes/components/ChildUniverseDeploymentModal.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { ChildUniverseDetails } from '../../universes/components/ChildUniverseDetails.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { EntityCard } from '../../../components/EntityCard.js' -import { ChildUniversesSection, ChildUniverseStatusBadge } from '../../universes/components/ChildUniversesSection.js' -import { Question } from './Question.js' -import { MetricField } from '../../../components/MetricField.js' +import { ChildUniverseDeploymentModal } from '@zoltar/ui-zoltar/features/universes/components/ChildUniverseDeploymentModal.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { ChildUniverseDetails } from '@zoltar/ui-zoltar/features/universes/components/ChildUniverseDetails.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { EntityCard } from '@zoltar/ui-core-shared/components/EntityCard.js' +import { ChildUniversesSection, ChildUniverseStatusBadge } from '@zoltar/ui-zoltar/features/universes/components/ChildUniversesSection.js' +import { Question } from '@zoltar/ui-core-shared/components/Question.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' import { ScalarDeploymentSection } from './ScalarDeploymentSection.js' -import { StateHint } from '../../../components/StateHint.js' -import { TimestampValue } from '../../../components/TimestampValue.js' -import { WorkflowSubsection } from '../../../components/WorkflowSubsection.js' -import { WalletAssetControl } from '../../../components/WalletAssetControl.js' -import type { LoadableValueState } from '../../../lib/loadState.js' -import { getUniversePresentation } from '../../../lib/userCopy.js' -import type { ZoltarUniverseSummary } from '../../../types/contracts.js' -import { getWrongNetworkMessage } from '../../../lib/network.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' +import { WorkflowSubsection } from '@zoltar/ui-core-shared/components/WorkflowSubsection.js' +import { WalletAssetControl } from '@zoltar/ui-core-shared/components/WalletAssetControl.js' +import type { LoadableValueState } from '@zoltar/ui-core-shared/lib/loadState.js' +import { getUniversePresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWrongNetworkMessage } from '@zoltar/ui-core-shared/lib/network.js' type MarketOverviewSectionProps = { accountAddress: Address | undefined isOnActiveAppChain: boolean diff --git a/ui/ts/features/markets/components/ScalarCreatePreview.tsx b/ui/statoblast/ts/features/markets/components/ScalarCreatePreview.tsx similarity index 85% rename from ui/ts/features/markets/components/ScalarCreatePreview.tsx rename to ui/statoblast/ts/features/markets/components/ScalarCreatePreview.tsx index b58607cd7..0b706c3de 100644 --- a/ui/ts/features/markets/components/ScalarCreatePreview.tsx +++ b/ui/statoblast/ts/features/markets/components/ScalarCreatePreview.tsx @@ -1,8 +1,8 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as marketCopy from '@zoltar/ui-zoltar/copy/market.js' import { useEffect, useState } from 'preact/hooks' -import { ScalarOutcomePicker } from './ScalarOutcomePicker.js' -import { clampScalarTickIndex, formatScalarOutcomeLabel } from '../lib/scalarOutcome.js' +import { ScalarOutcomePicker } from '@zoltar/ui-core-shared/components/ScalarOutcomePicker.js' +import { clampScalarTickIndex, formatScalarOutcomeLabel } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' export type ScalarCreatePreviewDetails = { answerUnit: string diff --git a/ui/ts/features/markets/components/ScalarDeploymentSection.tsx b/ui/statoblast/ts/features/markets/components/ScalarDeploymentSection.tsx similarity index 87% rename from ui/ts/features/markets/components/ScalarDeploymentSection.tsx rename to ui/statoblast/ts/features/markets/components/ScalarDeploymentSection.tsx index eba1515d7..a55161182 100644 --- a/ui/ts/features/markets/components/ScalarDeploymentSection.tsx +++ b/ui/statoblast/ts/features/markets/components/ScalarDeploymentSection.tsx @@ -1,18 +1,18 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as marketCopy from '@zoltar/ui-zoltar/copy/market.js' import { useEffect, useState } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { ChildUniversesSection, ChildUniverseStatusBadge } from '../../universes/components/ChildUniversesSection.js' -import { ActionLauncherButton } from '../../../components/ActionLauncherButton.js' -import { ChildUniverseDetails } from '../../universes/components/ChildUniverseDetails.js' -import { ChildUniverseDeploymentModal } from '../../universes/components/ChildUniverseDeploymentModal.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { ScalarOutcomePicker } from './ScalarOutcomePicker.js' -import { WorkflowSubsection } from '../../../components/WorkflowSubsection.js' -import { clampScalarTickIndex, formatScalarOutcomeLabel, getScalarOutcomeIndex } from '../lib/scalarOutcome.js' -import type { MarketDetails, ZoltarChildUniverseSummary } from '../../../types/contracts.js' -import { getWrongNetworkMessage } from '../../../lib/network.js' +import { ChildUniversesSection, ChildUniverseStatusBadge } from '@zoltar/ui-zoltar/features/universes/components/ChildUniversesSection.js' +import { ActionLauncherButton } from '@zoltar/ui-core-shared/components/ActionLauncherButton.js' +import { ChildUniverseDetails } from '@zoltar/ui-zoltar/features/universes/components/ChildUniverseDetails.js' +import { ChildUniverseDeploymentModal } from '@zoltar/ui-zoltar/features/universes/components/ChildUniverseDeploymentModal.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { ScalarOutcomePicker } from '@zoltar/ui-core-shared/components/ScalarOutcomePicker.js' +import { WorkflowSubsection } from '@zoltar/ui-core-shared/components/WorkflowSubsection.js' +import { clampScalarTickIndex, formatScalarOutcomeLabel, getScalarOutcomeIndex } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' +import type { MarketDetails, ZoltarChildUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWrongNetworkMessage } from '@zoltar/ui-core-shared/lib/network.js' type ScalarDeploymentSectionProps = { accountAddress: Address | undefined childUniverses: ZoltarChildUniverseSummary[] diff --git a/ui/ts/features/markets/components/TradingSection.tsx b/ui/statoblast/ts/features/markets/components/TradingSection.tsx similarity index 93% rename from ui/ts/features/markets/components/TradingSection.tsx rename to ui/statoblast/ts/features/markets/components/TradingSection.tsx index 92bbd8ced..b4599ba52 100644 --- a/ui/ts/features/markets/components/TradingSection.tsx +++ b/ui/statoblast/ts/features/markets/components/TradingSection.tsx @@ -1,28 +1,28 @@ -import * as commonCopy from '../../../copy/common.js' -import * as tradingCopy from '../../../copy/trading.js' -import * as transactionReviewCopy from '../../../copy/transactionReview.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as tradingCopy from '@zoltar/ui-zoltar/copy/trading.js' +import * as transactionReviewCopy from '@zoltar/ui-core-shared/copy/transactionReview.js' import { useState } from 'preact/hooks' import { zeroAddress } from '@zoltar/shared/ethereum' -import { ActionLauncherCard } from '../../../components/ActionLauncherCard.js' -import { AddressValue } from '../../../components/AddressValue.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { EnumDropdown } from '../../../components/EnumDropdown.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' -import { OperationModal } from '../../../components/OperationModal.js' -import { RankedBarList } from '../../../components/RankedBarList.js' -import { RouteWorkflowPanel } from '../../../components/RouteWorkflowPanel.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { ShareMigrationTargetsSection } from '../../universes/components/ShareMigrationTargetsSection.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { useChainTimestamp } from '../../../lib/chainTimestamp.js' -import { formatCurrencyInputBalance } from '../../../lib/formatters.js' -import { tryParseBigIntListInput } from '../../../lib/inputs.js' -import { getWrongNetworkMessage, isActiveAppChain } from '../../../lib/network.js' -import { getReportingOutcomeLabel, REPORTING_OUTCOME_DROPDOWN_OPTIONS } from '../../reporting/lib/reporting.js' +import { ActionLauncherCard } from '@zoltar/ui-core-shared/components/ActionLauncherCard.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { EnumDropdown } from '@zoltar/ui-core-shared/components/EnumDropdown.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { OperationModal } from '@zoltar/ui-core-shared/components/OperationModal.js' +import { RankedBarList } from '@zoltar/ui-core-shared/components/RankedBarList.js' +import { RouteWorkflowPanel } from '@zoltar/ui-core-shared/components/RouteWorkflowPanel.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { ShareMigrationTargetsSection } from '@zoltar/ui-zoltar/features/universes/components/ShareMigrationTargetsSection.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { formatCurrencyInputBalance } from '@zoltar/ui-core-shared/lib/formatters.js' +import { tryParseBigIntListInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { getWrongNetworkMessage, isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { getReportingOutcomeLabel, REPORTING_OUTCOME_DROPDOWN_OPTIONS } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' import { deriveSecurityPoolLifecycleState, evaluateSecurityPoolState } from '../../security-pools/lib/securityPoolState.js' import { calculateMintingCapacityAttoEth, @@ -44,7 +44,7 @@ import { NO_MINT_CAPACITY_NO_ACTIVE_CAPACITY_OWNERSHIP_MESSAGE, UNDEFINED_COMPLETE_SET_EXCHANGE_RATE_MESSAGE, } from '../lib/trading.js' -import { tryParseTradingAmountInput } from '../lib/marketForm.js' +import { tryParseTradingAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' import type { ReadinessAction } from '../../types.js' import type { TradingSectionProps } from '../../types.js' type TradingActionModal = 'mint' | 'redeem-complete-sets' | 'migrate-shares' | 'redeem-shares' | undefined diff --git a/ui/ts/features/markets/hooks/useMarketCreation.ts b/ui/statoblast/ts/features/markets/hooks/useMarketCreation.ts similarity index 93% rename from ui/ts/features/markets/hooks/useMarketCreation.ts rename to ui/statoblast/ts/features/markets/hooks/useMarketCreation.ts index 71a27558c..8fc64e300 100644 --- a/ui/ts/features/markets/hooks/useMarketCreation.ts +++ b/ui/statoblast/ts/features/markets/hooks/useMarketCreation.ts @@ -2,18 +2,19 @@ import { useSignal } from '@preact/signals' import { useEffect } from 'preact/hooks' import type { Address, Hash } from '@zoltar/shared/ethereum' import { createMarket as createMarketTransaction } from '../../../protocol/index.js' -import { createWalletWriteClient } from '../../../lib/clients.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createMarketCreationSuccessPresentation, createMarketCreationTransactionIntent, createMarketCreationWarningPresentation } from '../../transactionPresentations.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' -import { runWriteAction } from '../../../lib/writeAction.js' -import { createMarketParameters, hasDeployedStep } from '../lib/marketCreation.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' +import { runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { createMarketParameters } from '../lib/marketCreation.js' +import { hasDeployedStep } from '@zoltar/ui-core-shared/lib/deploymentStatus.js' import { getDefaultMarketFormState } from '../lib/marketForm.js' -import { getBrowserStorage } from '../../../lib/browserStorage.js' +import { getBrowserStorage } from '@zoltar/ui-core-shared/lib/browserStorage.js' import type { MarketFormState, WriteOperationsParameters } from '../../../types/app.js' -import type { DeploymentStatus, MarketCreationResult } from '../../../types/contracts.js' -import { useZoltarOperations } from '../../universes/hooks/useZoltarOperations.js' +import type { DeploymentStatus, MarketCreationResult } from '@zoltar/ui-core-shared/types/contracts.js' +import { useZoltarOperations } from '@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js' type UseMarketCreationParameters = { accountAddress: Address | undefined diff --git a/ui/ts/features/markets/hooks/useTradingOperations.ts b/ui/statoblast/ts/features/markets/hooks/useTradingOperations.ts similarity index 94% rename from ui/ts/features/markets/hooks/useTradingOperations.ts rename to ui/statoblast/ts/features/markets/hooks/useTradingOperations.ts index 66f6d03c8..495c63286 100644 --- a/ui/ts/features/markets/hooks/useTradingOperations.ts +++ b/ui/statoblast/ts/features/markets/hooks/useTradingOperations.ts @@ -2,23 +2,24 @@ import { useSignal } from '@preact/signals' import { useEffect, useRef } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' import { createCompleteSetInSecurityPool, loadSecurityPoolMintCapacity, loadTradingDetails as loadTradingDetailsForPool, loadZoltarUniverseSummary, migrateSharesFromUniverse, redeemCompleteSetInSecurityPool, redeemSharesInSecurityPool } from '../../../protocol/index.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { assertNever } from '../../../lib/assert.js' -import { normalizeAddress } from '../../../lib/address.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { getErrorMessage } from '../../../lib/errors.js' -import { parseAddressInput, parseBigIntListInput, parseReportingOutcomeInput, tryParseAddressInput } from '../../../lib/inputs.js' -import { getDefaultTradingFormState, parseTradingAmountInput } from '../lib/marketForm.js' -import { isActiveAppChain } from '../../../lib/network.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { normalizeAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' +import { parseAddressInput, parseBigIntListInput, parseReportingOutcomeInput, tryParseAddressInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { parseTradingAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { getDefaultTradingFormState } from '../lib/marketForm.js' +import { isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' import { convertSettlementCollateralAttoEthToAttoShares, estimateMintCheckpoint, getDefaultShareMigrationTargetOutcomeIndexes, getTradingMigrateSharesGuardMessage, getTradingMintGuardMessage, getTradingRedeemCompleteSetGuardMessage, getTradingRedeemSharesGuardMessage, isTradingSystemDeployed } from '../lib/trading.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createTradingSuccessPresentation, createTradingTransactionIntent, createTradingWarningPresentation } from '../../transactionPresentations.js' -import { buildWriteActionConfig, runWriteAction } from '../../../lib/writeAction.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { buildWriteActionConfig, runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { TradingFormState, WriteOperationsParameters } from '../../../types/app.js' -import type { DeploymentStatus, TradingActionResult, TradingDetails, ZoltarUniverseSummary } from '../../../types/contracts.js' +import type { DeploymentStatus, TradingActionResult, TradingDetails, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type UseTradingOperationsParameters = WriteOperationsParameters & { deploymentStatuses: DeploymentStatus[] diff --git a/ui/ts/features/markets/lib/marketCreation.ts b/ui/statoblast/ts/features/markets/lib/marketCreation.ts similarity index 93% rename from ui/ts/features/markets/lib/marketCreation.ts rename to ui/statoblast/ts/features/markets/lib/marketCreation.ts index f9da1a97f..355793669 100644 --- a/ui/ts/features/markets/lib/marketCreation.ts +++ b/ui/statoblast/ts/features/markets/lib/marketCreation.ts @@ -1,19 +1,18 @@ import { sortStringArrayByKeccak } from '@zoltar/shared/sortStringArrayByKeccak' import type { MarketFormState, SecurityPoolFormState } from '../../../types/app.js' -import type { DeploymentStatus, QuestionData } from '../../../types/contracts.js' -import { assertNever } from '../../../lib/assert.js' -import { parseDecimalInput } from '../../../lib/decimal.js' -import { parseStatoblastSecurityMultiplierBpsInput, parseTimestampInput, tryParseBigIntInput, tryParseTimestampInput } from './marketForm.js' -import { parseScalarFormInputs } from './scalarOutcome.js' +import type { QuestionData } from '@zoltar/ui-core-shared/types/contracts.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { parseDecimalInput } from '@zoltar/ui-core-shared/lib/decimal.js' +import { tryParseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { parseTimestampInput, tryParseTimestampInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { parseStatoblastSecurityMultiplierBpsInput } from './marketForm.js' +import { parseScalarFormInputs } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' type MarketFormField = keyof Pick type MarketFormValidation = { fieldErrors: Partial> isValid: boolean notice: string | undefined } -export function hasDeployedStep(steps: DeploymentStatus[], stepId: DeploymentStatus['id']) { - return steps.some(step => step.id === stepId && step.deployed) -} function getScalarQuestionData(form: MarketFormState) { return { answerUnit: form.answerUnit.trim(), diff --git a/ui/statoblast/ts/features/markets/lib/marketForm.ts b/ui/statoblast/ts/features/markets/lib/marketForm.ts new file mode 100644 index 000000000..2f8201371 --- /dev/null +++ b/ui/statoblast/ts/features/markets/lib/marketForm.ts @@ -0,0 +1,77 @@ +import type { ForkAuctionFormState, MarketFormState, SecurityPoolFormState, SecurityVaultFormState, TradingFormState } from '../../../types/app.js' +import { DEFAULT_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS } from '@zoltar/shared/oracleInitialReport' +import { parseDecimalInput, tryParseDecimalInput } from '@zoltar/ui-core-shared/lib/decimal.js' +import { formatCurrencyInputBalance } from '@zoltar/ui-core-shared/lib/formatters.js' + +const STATOBLAST_SECURITY_MULTIPLIER_DECIMALS = 4 + +export function getDefaultMarketFormState(): MarketFormState { + return { + answerUnit: '', + categoricalOutcomes: ['Yes', 'No'], + description: '', + endTime: '', + marketType: 'binary', + scalarIncrement: '1', + scalarMax: '100', + scalarMin: '0', + title: '', + startTime: '', + } +} + +export function getDefaultSecurityPoolFormState(): SecurityPoolFormState { + return { + initialReportPriorityFeeGwei: formatCurrencyInputBalance(DEFAULT_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS, 9), + marketId: '', + statoblastSecurityMultiplierBps: '2', + } +} + +export function parseStatoblastSecurityMultiplierBpsInput(value: string) { + return parseDecimalInput(value, 'Statoblast security multiplier', STATOBLAST_SECURITY_MULTIPLIER_DECIMALS) +} + +export function tryParseStatoblastSecurityMultiplierBpsInput(value: string) { + return tryParseDecimalInput(value, STATOBLAST_SECURITY_MULTIPLIER_DECIMALS) +} + +export function getDefaultSecurityVaultFormState(): SecurityVaultFormState { + return { + depositAmount: '0', + targetHealthFactor: '2', + repWithdrawAmount: '0', + selectedVaultOwner: '', + securityPoolAddress: '', + stagedOperationTimeoutMinutes: '5', + } +} + +export function getDefaultTradingFormState(): TradingFormState { + return { + completeSetAmount: '0', + redeemAmount: '0', + securityPoolAddress: '', + selectedShareOutcome: 'yes', + targetOutcomeIndexes: '', + } +} + +export function getDefaultForkAuctionFormState(): ForkAuctionFormState { + return { + claimBidIndex: '0', + claimBidTick: '0', + depositIndexes: '', + directForkQuestionId: '', + directForkUniverseId: '0', + refundBidIndex: '0', + refundTick: '0', + repMigrationOutcomes: 'yes', + securityPoolAddress: '', + selectedOutcome: 'yes', + settlementAddress: '', + submitBidAmount: '0', + submitBidPrice: '0', + vaultAddress: '', + } +} diff --git a/ui/ts/features/markets/lib/trading.ts b/ui/statoblast/ts/features/markets/lib/trading.ts similarity index 96% rename from ui/ts/features/markets/lib/trading.ts rename to ui/statoblast/ts/features/markets/lib/trading.ts index 976c622b2..35bb16559 100644 --- a/ui/ts/features/markets/lib/trading.ts +++ b/ui/statoblast/ts/features/markets/lib/trading.ts @@ -1,13 +1,13 @@ import type { Address } from '@zoltar/shared/ethereum' -import { getWalletActiveAppChainGuardState } from '../../../lib/actionGuards.js' -import { assertNever } from '../../../lib/assert.js' -import { formatAdditionalCurrencyBalance, formatCurrencyBalance, formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' -import { tryParseBigIntListInput } from '../../../lib/inputs.js' -import { tryParseTradingAmountInput } from './marketForm.js' -import { getReportingOutcomeLabel } from '../../reporting/lib/reporting.js' -import { isValidScalarOutcomeIndex } from './scalarOutcome.js' -import type { DeploymentStatus } from '../../../types/contracts.js' -import type { ReportingOutcomeKey, TradingShareBalances, ZoltarUniverseSummary } from '../../../types/contracts.js' +import { getWalletActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { formatAdditionalCurrencyBalance, formatCurrencyBalance, formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { tryParseBigIntListInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { tryParseTradingAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { getReportingOutcomeLabel } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' +import { isValidScalarOutcomeIndex } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' +import type { DeploymentStatus } from '@zoltar/ui-core-shared/types/contracts.js' +import type { ReportingOutcomeKey, TradingShareBalances, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' const PRICE_PRECISION = 10n ** 18n const PERCENT_MULTIPLIER = 100n diff --git a/ui/ts/features/security-pools/components/CollateralizationCircle.tsx b/ui/statoblast/ts/features/security-pools/components/CollateralizationCircle.tsx similarity index 96% rename from ui/ts/features/security-pools/components/CollateralizationCircle.tsx rename to ui/statoblast/ts/features/security-pools/components/CollateralizationCircle.tsx index cf6960f5f..606c84fce 100644 --- a/ui/ts/features/security-pools/components/CollateralizationCircle.tsx +++ b/ui/statoblast/ts/features/security-pools/components/CollateralizationCircle.tsx @@ -1,6 +1,6 @@ -import * as pricingCopy from '../../../copy/pricing.js' +import * as pricingCopy from '@zoltar/ui-core-shared/copy/pricing.js' import type { CollateralizationCircleProps } from '../../types.js' -import { formatCollateralizationCompactPercentLabel, getCollateralizationVisualPercent, getToneRatioThreshold, getVisualRatio } from '../../../lib/visualMetrics.js' +import { formatCollateralizationCompactPercentLabel, getCollateralizationVisualPercent, getToneRatioThreshold, getVisualRatio } from '@zoltar/ui-core-shared/lib/visualMetrics.js' const MAX_RING_COLLATERALIZATION_PERCENT = 999n * 10n ** 18n diff --git a/ui/ts/features/security-pools/components/CollateralizationMetricField.tsx b/ui/statoblast/ts/features/security-pools/components/CollateralizationMetricField.tsx similarity index 84% rename from ui/ts/features/security-pools/components/CollateralizationMetricField.tsx rename to ui/statoblast/ts/features/security-pools/components/CollateralizationMetricField.tsx index 373c70f9b..f10245908 100644 --- a/ui/ts/features/security-pools/components/CollateralizationMetricField.tsx +++ b/ui/statoblast/ts/features/security-pools/components/CollateralizationMetricField.tsx @@ -1,9 +1,9 @@ -import * as commonCopy from '../../../copy/common.js' -import * as pricingCopy from '../../../copy/pricing.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as pricingCopy from '@zoltar/ui-core-shared/copy/pricing.js' import type { ComponentChildren } from 'preact' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { MetricField } from '../../../components/MetricField.js' -import { getRepPriceSourceCopy, renderRepPriceSourceLabel, type RepPriceSource } from '../../open-oracle/lib/repPriceSource.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { getRepPriceSourceCopy, renderRepPriceSourceLabel, type RepPriceSource } from '@zoltar/ui-zoltar/features/open-oracle/lib/repPriceSource.js' import { getCollateralizationDisplayState, getCollateralizationTone } from '../../markets/lib/trading.js' type CollateralizationMetricFieldProps = { className?: string | undefined diff --git a/ui/ts/features/security-pools/components/LiquidationModal.tsx b/ui/statoblast/ts/features/security-pools/components/LiquidationModal.tsx similarity index 94% rename from ui/ts/features/security-pools/components/LiquidationModal.tsx rename to ui/statoblast/ts/features/security-pools/components/LiquidationModal.tsx index f0ab7d54f..26bff43ba 100644 --- a/ui/ts/features/security-pools/components/LiquidationModal.tsx +++ b/ui/statoblast/ts/features/security-pools/components/LiquidationModal.tsx @@ -1,38 +1,39 @@ -import * as commonCopy from '../../../copy/common.js' -import * as liquidationCopy from '../../../copy/liquidation.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as liquidationCopy from '@zoltar/ui-zoltar/copy/liquidation.js' import { useEffect, useId, useRef } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { AddressInfo } from '../../../components/AddressInfo.js' -import { AddressValue } from '../../../components/AddressValue.js' -import { Badge } from '../../../components/Badge.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' -import { OpenOraclePriceValue } from '../../open-oracle/components/OpenOraclePriceValue.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { TransactionStatusCard } from '../../../components/TransactionStatusCard.js' -import { TimestampValue } from '../../../components/TimestampValue.js' -import { assertNever } from '../../../lib/assert.js' -import { sameAddress } from '../../../lib/address.js' -import { tryParseAddressInput } from '../../../lib/inputs.js' -import { pickFirstReason } from '../../../lib/actionAvailability.js' -import { useChainTimestamp } from '../../../lib/chainTimestamp.js' -import { formatCurrencyInputBalance, formatDuration } from '../../../lib/formatters.js' +import { AddressInfo } from '@zoltar/ui-core-shared/components/AddressInfo.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { OpenOraclePriceValue } from '@zoltar/ui-zoltar/features/open-oracle/components/OpenOraclePriceValue.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' +import { TransactionStatusCard } from '@zoltar/ui-core-shared/components/TransactionStatusCard.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { tryParseAddressInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { pickFirstReason } from '@zoltar/ui-core-shared/lib/actionAvailability.js' +import { useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { formatCurrencyInputBalance, formatDuration } from '@zoltar/ui-core-shared/lib/formatters.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' import { getDeterministicLiquidationFailureReason, getLiquidationExecutionFailureDetail, getLiquidationFailureReason, getMaxLiquidationAmount, simulateLiquidation } from '../lib/liquidation.js' -import { tryParseBigIntInput, tryParseEthAmountInput } from '../../markets/lib/marketForm.js' -import { getOracleRequestEthGuardMessage } from '../../open-oracle/lib/oracleRequestEth.js' -import { getRepPriceSourceCopy, renderRepPriceSourceLabel, type RepPriceSource } from '../../open-oracle/lib/repPriceSource.js' +import { tryParseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { tryParseEthAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { getOracleRequestEthGuardMessage } from '@zoltar/ui-zoltar/features/open-oracle/lib/oracleRequestEth.js' +import { getRepPriceSourceCopy, renderRepPriceSourceLabel, type RepPriceSource } from '@zoltar/ui-zoltar/features/open-oracle/lib/repPriceSource.js' import { getStagedOperationTimeoutSeconds, isOracleManagerPriceUsable } from '../lib/securityVault.js' import { formatStatoblastSecurityMultiplier } from '../../markets/lib/trading.js' -import { useModalFocusIsolation } from '../../../hooks/useModalFocusIsolation.js' +import { useModalFocusIsolation } from '@zoltar/ui-core-shared/hooks/useModalFocusIsolation.js' import type { SecurityPoolStateModel } from '../lib/securityPoolState.js' -import type { LiquidationApprovalDetails, LiquidationFundingPreview, ListedSecurityPool, OracleManagerDetails, SecurityPoolOverviewActionResult, SecurityPoolVaultSummary } from '../../../types/contracts.js' -import { getWrongNetworkMessage } from '../../../lib/network.js' +import type { LiquidationApprovalDetails, LiquidationFundingPreview, ListedSecurityPool, OracleManagerDetails, SecurityPoolOverviewActionResult, SecurityPoolVaultSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWrongNetworkMessage } from '@zoltar/ui-core-shared/lib/network.js' type LiquidationModalProps = { accountAddress: Address | undefined closeLiquidationModal: () => void diff --git a/ui/ts/features/security-pools/components/SecurityPoolLink.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityPoolLink.tsx similarity index 100% rename from ui/ts/features/security-pools/components/SecurityPoolLink.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityPoolLink.tsx diff --git a/ui/ts/features/security-pools/components/SecurityPoolSection.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityPoolSection.tsx similarity index 90% rename from ui/ts/features/security-pools/components/SecurityPoolSection.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityPoolSection.tsx index 211af007a..78c5fc987 100644 --- a/ui/ts/features/security-pools/components/SecurityPoolSection.tsx +++ b/ui/statoblast/ts/features/security-pools/components/SecurityPoolSection.tsx @@ -1,26 +1,26 @@ -import * as commonCopy from '../../../copy/common.js' -import * as securityPoolCopy from '../../../copy/securityPool.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' import type { ComponentChildren } from 'preact' import { useEffect, useRef, useState } from 'preact/hooks' -import { AddressValue } from '../../../components/AddressValue.js' -import { EntityCard } from '../../../components/EntityCard.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { LookupFieldRow } from '../../../components/LookupFieldRow.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { Question, getQuestionTitle } from '../../markets/components/Question.js' -import { RouteWorkflowPanel } from '../../../components/RouteWorkflowPanel.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionHashLink } from '../../../components/TransactionHashLink.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { isActiveAppChain } from '../../../lib/network.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { EntityCard } from '@zoltar/ui-core-shared/components/EntityCard.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { LookupFieldRow } from '@zoltar/ui-core-shared/components/LookupFieldRow.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { Question, getQuestionTitle } from '@zoltar/ui-core-shared/components/Question.js' +import { RouteWorkflowPanel } from '@zoltar/ui-core-shared/components/RouteWorkflowPanel.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionHashLink } from '@zoltar/ui-core-shared/components/TransactionHashLink.js' +import { isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' import { formatOpenInterestFeePerYearPercent, ORIGIN_POOL_INITIAL_RETENTION_RATE } from '../lib/retentionRate.js' -import { formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' +import { formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' import { getInitialReportPriorityFeeValidationMessage, getSecurityPoolCreateDisabledReason, getStatoblastSecurityMultiplierValidationMessage } from '../lib/securityPoolCreationGuards.js' import { formatStatoblastSecurityMultiplier } from '../../markets/lib/trading.js' import type { SecurityPoolSectionProps } from '../../types.js' -import { formatUniverseIdHex } from '../../universes/lib/universe.js' +import { formatUniverseIdHex } from '@zoltar/ui-zoltar/features/universes/lib/universe.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' export function SecurityPoolSection({ accountState, diff --git a/ui/ts/features/security-pools/components/SecurityPoolSummaryMetrics.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityPoolSummaryMetrics.tsx similarity index 85% rename from ui/ts/features/security-pools/components/SecurityPoolSummaryMetrics.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityPoolSummaryMetrics.tsx index 22ded2aef..1e324f222 100644 --- a/ui/ts/features/security-pools/components/SecurityPoolSummaryMetrics.tsx +++ b/ui/statoblast/ts/features/security-pools/components/SecurityPoolSummaryMetrics.tsx @@ -1,18 +1,18 @@ -import * as commonCopy from '../../../copy/common.js' -import * as securityPoolCopy from '../../../copy/securityPool.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' import type { ComponentChildren } from 'preact' -import { AddressValue } from '../../../components/AddressValue.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' -import { OpenOraclePriceValue } from '../../open-oracle/components/OpenOraclePriceValue.js' -import { ProgressMeter } from '../../../components/ProgressMeter.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { OpenOraclePriceValue } from '@zoltar/ui-zoltar/features/open-oracle/components/OpenOraclePriceValue.js' +import { ProgressMeter } from '@zoltar/ui-core-shared/components/ProgressMeter.js' import { openInterestFeePerYearBigint } from '../lib/retentionRate.js' import { calculateMintingCapacityAttoEth, formatStatoblastSecurityMultiplier } from '../../markets/lib/trading.js' -import { getToneRatioThreshold, getVisualRatio } from '../../../lib/visualMetrics.js' -import { formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' +import { getToneRatioThreshold, getVisualRatio } from '@zoltar/ui-core-shared/lib/visualMetrics.js' +import { formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' import type { MetricGridVariant } from '../../types.js' -import type { ListedSecurityPool } from '../../../types/contracts.js' +import type { ListedSecurityPool } from '@zoltar/ui-core-shared/types/contracts.js' type SecurityPoolSummaryMetricsProps = { children?: ComponentChildren diff --git a/ui/ts/features/security-pools/components/SecurityPoolVaultDirectory.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityPoolVaultDirectory.tsx similarity index 92% rename from ui/ts/features/security-pools/components/SecurityPoolVaultDirectory.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityPoolVaultDirectory.tsx index 612d1af78..6bd52f570 100644 --- a/ui/ts/features/security-pools/components/SecurityPoolVaultDirectory.tsx +++ b/ui/statoblast/ts/features/security-pools/components/SecurityPoolVaultDirectory.tsx @@ -1,8 +1,8 @@ -import * as securityPoolCopy from '../../../copy/securityPool.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' import type { ComponentChildren } from 'preact' -import { AddressValue } from '../../../components/AddressValue.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' import { VaultMetricGrid } from './VaultMetricGrid.js' -import type { ListedSecurityPool, SecurityPoolVaultSummary } from '../../../types/contracts.js' +import type { ListedSecurityPool, SecurityPoolVaultSummary } from '@zoltar/ui-core-shared/types/contracts.js' type SecurityPoolVaultDirectoryProps = { emptyState: ComponentChildren diff --git a/ui/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx similarity index 95% rename from ui/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx index 28181d1aa..9d2b46cc2 100644 --- a/ui/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx +++ b/ui/statoblast/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx @@ -1,44 +1,44 @@ -import * as commonCopy from '../../../copy/common.js' -import * as securityPoolCopy from '../../../copy/securityPool.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' import type { ComponentChildren } from 'preact' import { useEffect, useRef, useState } from 'preact/hooks' import { getAddress, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { AddressValue } from '../../../components/AddressValue.js' -import { Badge } from '../../../components/Badge.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' import { ForkAuctionSection } from '../../truth-auctions/components/ForkAuctionSection.js' import { LiquidationModal } from './LiquidationModal.js' -import { LookupFieldRow } from '../../../components/LookupFieldRow.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' -import { OpenOraclePriceValue } from '../../open-oracle/components/OpenOraclePriceValue.js' -import { getQuestionTitle, Question } from '../../markets/components/Question.js' -import { ReportingSection } from '../../reporting/components/ReportingSection.js' -import { RouteWorkflowPanel } from '../../../components/RouteWorkflowPanel.js' +import { LookupFieldRow } from '@zoltar/ui-core-shared/components/LookupFieldRow.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { OpenOraclePriceValue } from '@zoltar/ui-zoltar/features/open-oracle/components/OpenOraclePriceValue.js' +import { getQuestionTitle, Question } from '@zoltar/ui-core-shared/components/Question.js' +import { ReportingSection } from '@zoltar/ui-zoltar/features/reporting/components/ReportingSection.js' +import { RouteWorkflowPanel } from '@zoltar/ui-core-shared/components/RouteWorkflowPanel.js' import { SecurityPoolSummaryMetrics } from './SecurityPoolSummaryMetrics.js' import { SecurityPoolLink } from './SecurityPoolLink.js' import { SecurityPoolVaultDirectory } from './SecurityPoolVaultDirectory.js' -import { SectionBlock } from '../../../components/SectionBlock.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' import { getQueuedVaultOperation, SecurityVaultSection } from './SecurityVaultSection.js' -import { StickyObjectContext } from '../../../components/StickyObjectContext.js' -import { StateHint } from '../../../components/StateHint.js' +import { StickyObjectContext } from '@zoltar/ui-core-shared/components/StickyObjectContext.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' import { TradingSection } from '../../markets/components/TradingSection.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionNetworkValue } from '../../../components/TransactionNetworkValue.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { OperationModal } from '../../../components/OperationModal.js' -import * as transactionReviewCopy from '../../../copy/transactionReview.js' -import { UniverseLink } from '../../universes/components/UniverseLink.js' -import { ViewTabs } from '../../../components/ViewTabs.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { tryParseBigIntInput } from '../../markets/lib/marketForm.js' -import { assertNever } from '../../../lib/assert.js' -import { normalizeAddress, sameAddress } from '../../../lib/address.js' -import { getWrongNetworkMessage } from '../../../lib/network.js' -import { useChainTimestamp } from '../../../lib/chainTimestamp.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionNetworkValue } from '@zoltar/ui-core-shared/components/TransactionNetworkValue.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { OperationModal } from '@zoltar/ui-core-shared/components/OperationModal.js' +import * as transactionReviewCopy from '@zoltar/ui-core-shared/copy/transactionReview.js' +import { UniverseLink } from '@zoltar/ui-zoltar/features/universes/components/UniverseLink.js' +import { ViewTabs } from '@zoltar/ui-core-shared/components/ViewTabs.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' +import { tryParseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { normalizeAddress, sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { getWrongNetworkMessage } from '@zoltar/ui-core-shared/lib/network.js' +import { useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' import { applySelectedPoolWorkflowState, getCurrentSelectedPoolReportingDetails, @@ -63,22 +63,22 @@ import { shouldReloadSelectedPoolDetails, shouldShowSelectedPoolWorkflowDetails, } from '../lib/securityPoolWorkflow.js' -import { sameCaseInsensitiveText } from '../../../lib/caseInsensitive.js' +import { sameCaseInsensitiveText } from '@zoltar/ui-core-shared/lib/caseInsensitive.js' import { getLiquidationNoticeState } from '../lib/liquidationStatus.js' -import { resolveRequestedLoadableValueState } from '../../../lib/loadState.js' -import { isActiveAppChain } from '../../../lib/network.js' -import { getReportingLockedUntilMessage, hasReportingOpened } from '../../reporting/lib/reporting.js' -import { addOpenOracleBountyBuffer } from '../../open-oracle/lib/openOracle.js' +import { resolveRequestedLoadableValueState } from '@zoltar/ui-core-shared/lib/loadState.js' +import { isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { getReportingLockedUntilMessage, hasReportingOpened } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' +import { addOpenOracleBountyBuffer } from '@zoltar/ui-zoltar/features/open-oracle/lib/openOracle.js' import { getSecurityPoolStatusBadgeLabel } from '../lib/securityPoolLabels.js' import { deriveSecurityPoolLifecycleState, deriveSecurityPoolReportingStage, evaluateSecurityPoolState, type SecurityPoolLifecycleState } from '../lib/securityPoolState.js' import { getVaultExecutePendingOperationGuardMessage, getVaultRequestPriceGuardMessage } from '../lib/securityVaultGuards.js' import { doesLoadedSecurityVaultMatchSelection, doesSecurityVaultExistOnchain, getSelectedVaultOwner, isOracleManagerPriceUsable, isSelectedVaultOwnedByAccount as isSelectedVaultOwnedByAccountHelper } from '../lib/securityVault.js' -import { getPoolRegistryPresentation } from '../../../lib/userCopy.js' -import { formatUniverseIdHex } from '../../universes/lib/universe.js' +import { getPoolRegistryPresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import { formatUniverseIdHex } from '@zoltar/ui-zoltar/features/universes/lib/universe.js' import { useForkWorkflowSelectionState } from '../../truth-auctions/hooks/useForkWorkflowSelectionState.js' import { useSelectedVaultWorkflowState, type SelectedVaultView } from '../hooks/useSelectedVaultWorkflowState.js' import type { SecurityPoolWorkflowRouteContentProps, ViewTabOption } from '../../types.js' -import type { ForkAuctionDetails, ListedSecurityPool } from '../../../types/contracts.js' +import type { ForkAuctionDetails, ListedSecurityPool } from '@zoltar/ui-core-shared/types/contracts.js' function buildSelectedPoolSummaryPool({ forkAuctionDetails, selectedPool }: { forkAuctionDetails: ForkAuctionDetails | undefined; selectedPool: ListedSecurityPool | undefined }) { if (selectedPool === undefined) return undefined diff --git a/ui/ts/features/security-pools/components/SecurityPoolsOverviewSection.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityPoolsOverviewSection.tsx similarity index 87% rename from ui/ts/features/security-pools/components/SecurityPoolsOverviewSection.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityPoolsOverviewSection.tsx index a7d6be1dc..084bd493d 100644 --- a/ui/ts/features/security-pools/components/SecurityPoolsOverviewSection.tsx +++ b/ui/statoblast/ts/features/security-pools/components/SecurityPoolsOverviewSection.tsx @@ -1,31 +1,31 @@ -import * as commonCopy from '../../../copy/common.js' -import * as securityPoolCopy from '../../../copy/securityPool.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' import { useEffect, useRef, useState } from 'preact/hooks' import { zeroAddress } from '@zoltar/shared/ethereum' -import { AddressValue } from '../../../components/AddressValue.js' -import { IdentifierValue } from '../../../components/IdentifierValue.js' -import { Badge } from '../../../components/Badge.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { ComparisonRecord } from '../../../components/ComparisonRecord.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { MetricField } from '../../../components/MetricField.js' -import { OpenOraclePriceValue } from '../../open-oracle/components/OpenOraclePriceValue.js' -import { PaginationControls } from '../../../components/PaginationControls.js' -import { ReadOnlyDetailAccordion } from '../../../components/ReadOnlyDetailAccordion.js' -import { Question, getQuestionTitle } from '../../markets/components/Question.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { StateHint } from '../../../components/StateHint.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { getWalletScopedAccountAddress } from '../../../lib/network.js' -import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex, SECURITY_POOL_PAGE_SIZE } from '../../../lib/pagination.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { IdentifierValue } from '@zoltar/ui-core-shared/components/IdentifierValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { ComparisonRecord } from '@zoltar/ui-core-shared/components/ComparisonRecord.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { OpenOraclePriceValue } from '@zoltar/ui-zoltar/features/open-oracle/components/OpenOraclePriceValue.js' +import { PaginationControls } from '@zoltar/ui-core-shared/components/PaginationControls.js' +import { ReadOnlyDetailAccordion } from '@zoltar/ui-core-shared/components/ReadOnlyDetailAccordion.js' +import { Question, getQuestionTitle } from '@zoltar/ui-core-shared/components/Question.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { formatUniverseIdHex } from '@zoltar/ui-zoltar/features/universes/lib/universe.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' +import { getWalletScopedAccountAddress } from '@zoltar/ui-core-shared/lib/network.js' +import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex, SECURITY_POOL_PAGE_SIZE } from '@zoltar/ui-core-shared/lib/pagination.js' import { openInterestFeePerYearBigint } from '../lib/retentionRate.js' import { formatSecurityPoolPageSummary, getSecurityPoolStatusBadgeLabel } from '../lib/securityPoolLabels.js' import { deriveSecurityPoolLifecycleState, evaluateSecurityPoolState, type SecurityPoolLifecycleState } from '../lib/securityPoolState.js' import { calculateMintingCapacityAttoEth, formatStatoblastSecurityMultiplier } from '../../markets/lib/trading.js' -import { getPoolRegistryPresentation } from '../../../lib/userCopy.js' -import { formatUniverseIdHex } from '../../universes/lib/universe.js' +import { getPoolRegistryPresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' import type { SecurityPoolsOverviewSectionProps } from '../../types.js' export function SecurityPoolsOverviewSection({ diff --git a/ui/ts/features/security-pools/components/SecurityPoolsSection.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityPoolsSection.tsx similarity index 96% rename from ui/ts/features/security-pools/components/SecurityPoolsSection.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityPoolsSection.tsx index c0b304eae..eb1a33592 100644 --- a/ui/ts/features/security-pools/components/SecurityPoolsSection.tsx +++ b/ui/statoblast/ts/features/security-pools/components/SecurityPoolsSection.tsx @@ -1,7 +1,7 @@ import { SecurityPoolSection } from './SecurityPoolSection.js' import { SecurityPoolWorkflowSection } from './SecurityPoolWorkflowSection.js' import { SecurityPoolsOverviewSection } from './SecurityPoolsOverviewSection.js' -import { sameCaseInsensitiveText } from '../../../lib/caseInsensitive.js' +import { sameCaseInsensitiveText } from '@zoltar/ui-core-shared/lib/caseInsensitive.js' import type { SecurityPoolsSectionProps, SecurityPoolsView } from '../../types.js' export function shouldRefreshSelectedPoolDataOnViewOpen({ currentSecurityPoolAddress, nextSecurityPoolAddress, nextView, selectedPoolHasLoadedDetails }: { currentSecurityPoolAddress: string; nextSecurityPoolAddress?: string | undefined; nextView: SecurityPoolsView; selectedPoolHasLoadedDetails: boolean }) { diff --git a/ui/ts/features/security-pools/components/SecurityVaultSection.tsx b/ui/statoblast/ts/features/security-pools/components/SecurityVaultSection.tsx similarity index 94% rename from ui/ts/features/security-pools/components/SecurityVaultSection.tsx rename to ui/statoblast/ts/features/security-pools/components/SecurityVaultSection.tsx index 5b9b80690..80dc9d5a4 100644 --- a/ui/ts/features/security-pools/components/SecurityVaultSection.tsx +++ b/ui/statoblast/ts/features/security-pools/components/SecurityVaultSection.tsx @@ -1,41 +1,42 @@ -import * as commonCopy from '../../../copy/common.js' -import * as securityPoolCopy from '../../../copy/securityPool.js' -import * as transactionReviewCopy from '../../../copy/transactionReview.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' +import * as transactionReviewCopy from '@zoltar/ui-core-shared/copy/transactionReview.js' import { useEffect, useId, useRef, useState } from 'preact/hooks' -import { AddressValue } from '../../../components/AddressValue.js' -import { ActionLauncherCard } from '../../../components/ActionLauncherCard.js' -import { Badge } from '../../../components/Badge.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { EntityCard } from '../../../components/EntityCard.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { LookupFieldRow } from '../../../components/LookupFieldRow.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' -import { OperationModal } from '../../../components/OperationModal.js' -import { RouteWorkflowPanel } from '../../../components/RouteWorkflowPanel.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { StateHint } from '../../../components/StateHint.js' -import { TimestampValue } from '../../../components/TimestampValue.js' -import { TokenApprovalControl } from '../../../components/TokenApprovalControl.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionNetworkValue } from '../../../components/TransactionNetworkValue.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { ActionLauncherCard } from '@zoltar/ui-core-shared/components/ActionLauncherCard.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { EntityCard } from '@zoltar/ui-core-shared/components/EntityCard.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { LookupFieldRow } from '@zoltar/ui-core-shared/components/LookupFieldRow.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { OperationModal } from '@zoltar/ui-core-shared/components/OperationModal.js' +import { RouteWorkflowPanel } from '@zoltar/ui-core-shared/components/RouteWorkflowPanel.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' +import { TokenApprovalControl } from '@zoltar/ui-core-shared/components/TokenApprovalControl.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionNetworkValue } from '@zoltar/ui-core-shared/components/TransactionNetworkValue.js' import { VaultMetricGrid } from './VaultMetricGrid.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { normalizeAddress, sameAddress } from '../../../lib/address.js' -import { formatCurrencyBalance, formatCurrencyInputBalance, formatDuration } from '../../../lib/formatters.js' -import { balanceShortage } from '../../../lib/inputs.js' -import { tryParseBigIntInput, tryParseRepAmountInput } from '../../markets/lib/marketForm.js' -import { isActiveAppChain } from '../../../lib/network.js' -import { resolveOracleOperationEthFunding } from '../../open-oracle/lib/oracleRequestEth.js' -import { getWalletActiveAppChainGuardState } from '../../../lib/actionGuards.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' +import { normalizeAddress, sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { formatCurrencyBalance, formatCurrencyInputBalance, formatDuration } from '@zoltar/ui-core-shared/lib/formatters.js' +import { balanceShortage } from '@zoltar/ui-core-shared/lib/inputs.js' +import { tryParseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { tryParseRepAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { resolveOracleOperationEthFunding } from '@zoltar/ui-zoltar/features/open-oracle/lib/oracleRequestEth.js' +import { getWalletActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' import { getSecurityPoolVaultReadinessActions } from '../lib/securityPoolReadiness.js' import { getVaultLauncherVaultOwnerReason, getVaultLauncherWalletReason } from '../lib/securityPoolLabels.js' import { isVaultHealthyAtFactor } from '../lib/liquidation.js' import { getTargetHealthFactorGuardMessage, getVaultDepositGuardMessage, getVaultRedeemRepGuardMessage, getVaultWithdrawGuardMessage } from '../lib/securityVaultGuards.js' -import { deriveTokenApprovalRequirement } from '../../../lib/tokenApproval.js' -import { useChainTimestamp } from '../../../lib/chainTimestamp.js' +import { deriveTokenApprovalRequirement } from '@zoltar/ui-core-shared/lib/tokenApproval.js' +import { useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' import { DEFAULT_STAGED_OPERATION_TIMEOUT_MINUTES, doesSecurityVaultExistOnchain, @@ -49,7 +50,7 @@ import { isSelectedVaultOwnedByAccount as isSelectedVaultOwnedByAccountHelper, MIN_SECURITY_VAULT_REP_DEPOSIT_ATTO_REP, } from '../lib/securityVault.js' -import type { StagedOracleOperation } from '../../../types/contracts.js' +import type { StagedOracleOperation } from '@zoltar/ui-core-shared/types/contracts.js' import type { ReadinessAction, SecurityVaultSectionProps } from '../../types.js' type SelectedVaultSummarySectionProps = Pick & { capacityOwnershipAttoRep: bigint diff --git a/ui/ts/features/security-pools/components/VaultMetricGrid.tsx b/ui/statoblast/ts/features/security-pools/components/VaultMetricGrid.tsx similarity index 91% rename from ui/ts/features/security-pools/components/VaultMetricGrid.tsx rename to ui/statoblast/ts/features/security-pools/components/VaultMetricGrid.tsx index f8f246ad3..a436f017d 100644 --- a/ui/ts/features/security-pools/components/VaultMetricGrid.tsx +++ b/ui/statoblast/ts/features/security-pools/components/VaultMetricGrid.tsx @@ -1,8 +1,8 @@ -import * as commonCopy from '../../../copy/common.js' -import * as securityPoolCopy from '../../../copy/securityPool.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { MetricField } from '../../../components/MetricField.js' -import { TimestampValue } from '../../../components/TimestampValue.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' import type { VaultMetricGridProps } from '../../types.js' function VaultPrimaryMetric({ className, label, suffix, value }: { className?: string; label: string; suffix: string; value: bigint | undefined }) { diff --git a/ui/ts/features/security-pools/hooks/useSecurityPoolCreation.ts b/ui/statoblast/ts/features/security-pools/hooks/useSecurityPoolCreation.ts similarity index 92% rename from ui/ts/features/security-pools/hooks/useSecurityPoolCreation.ts rename to ui/statoblast/ts/features/security-pools/hooks/useSecurityPoolCreation.ts index f3c78ec60..4506857fc 100644 --- a/ui/ts/features/security-pools/hooks/useSecurityPoolCreation.ts +++ b/ui/statoblast/ts/features/security-pools/hooks/useSecurityPoolCreation.ts @@ -2,19 +2,21 @@ import { useSignal } from '@preact/signals' import { useEffect } from 'preact/hooks' import type { Address, Hash } from '@zoltar/shared/ethereum' import { createSecurityPool, loadMarketDetails, originSecurityPoolExists } from '../../../protocol/index.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { getErrorMessage, isRecoverableContractReadError } from '../../../lib/errors.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { getErrorMessage, isRecoverableContractReadError } from '@zoltar/ui-core-shared/lib/errors.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createSecurityPoolCreationSuccessPresentation, createSecurityPoolCreationTransactionIntent, createSecurityPoolCreationWarningPresentation } from '../../transactionPresentations.js' -import { runWriteAction } from '../../../lib/writeAction.js' -import { createSecurityPoolParameters, hasDeployedStep } from '../../markets/lib/marketCreation.js' -import { getDefaultSecurityPoolFormState, tryParseBigIntInput, tryParseStatoblastSecurityMultiplierBpsInput } from '../../markets/lib/marketForm.js' -import { tryParseDecimalInput } from '../../../lib/decimal.js' +import { runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { createSecurityPoolParameters } from '../../markets/lib/marketCreation.js' +import { hasDeployedStep } from '@zoltar/ui-core-shared/lib/deploymentStatus.js' +import { tryParseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { getDefaultSecurityPoolFormState, tryParseStatoblastSecurityMultiplierBpsInput } from '../../markets/lib/marketForm.js' +import { tryParseDecimalInput } from '@zoltar/ui-core-shared/lib/decimal.js' import type { SecurityPoolFormState, WriteOperationsParameters } from '../../../types/app.js' -import type { DeploymentStatus, MarketDetails, SecurityPoolCreationResult } from '../../../types/contracts.js' +import type { DeploymentStatus, MarketDetails, SecurityPoolCreationResult } from '@zoltar/ui-core-shared/types/contracts.js' type UseSecurityPoolCreationParameters = { accountAddress: Address | undefined diff --git a/ui/ts/features/security-pools/hooks/useSecurityPoolsOverview.ts b/ui/statoblast/ts/features/security-pools/hooks/useSecurityPoolsOverview.ts similarity index 97% rename from ui/ts/features/security-pools/hooks/useSecurityPoolsOverview.ts rename to ui/statoblast/ts/features/security-pools/hooks/useSecurityPoolsOverview.ts index da4fe5a25..f77bb6753 100644 --- a/ui/ts/features/security-pools/hooks/useSecurityPoolsOverview.ts +++ b/ui/statoblast/ts/features/security-pools/hooks/useSecurityPoolsOverview.ts @@ -11,24 +11,25 @@ import { loadSecurityPoolVaultSummary as loadProtocolSecurityPoolVaultSummary, queueSecurityPoolLiquidation, } from '../../../protocol/index.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { normalizeAddress } from '../../../lib/address.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { getActiveBackend } from '../../../lib/activeEnvironment.js' -import { getErrorDetail, getErrorMessage } from '../../../lib/errors.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { normalizeAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getActiveBackend } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { getErrorDetail, getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createLiquidationFailurePresentation, createLiquidationSuccessPresentation, createLiquidationTransactionIntent, createLiquidationWarningPresentation } from '../../transactionPresentations.js' -import { buildWriteActionConfig, runWriteAction } from '../../../lib/writeAction.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' -import { parseAddressInput, parseBytes32Input, tryParseAddressInput } from '../../../lib/inputs.js' -import { parseBigIntInput, parseEthAmountInput } from '../../markets/lib/marketForm.js' -import { formatAdditionalCurrencyBalance } from '../../../lib/formatters.js' +import { buildWriteActionConfig, runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' +import { parseAddressInput, parseBytes32Input, tryParseAddressInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { parseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { parseEthAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { formatAdditionalCurrencyBalance } from '@zoltar/ui-core-shared/lib/formatters.js' import { getLiquidationExecutionFailureDetail } from '../lib/liquidation.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' import { DEFAULT_STAGED_OPERATION_TIMEOUT_MINUTES, getStagedOperationTimeoutSeconds, MAX_STAGED_OPERATION_TIMEOUT_MINUTES, MIN_STAGED_OPERATION_TIMEOUT_MINUTES } from '../lib/securityVault.js' import type { WriteOperationsParameters } from '../../../types/app.js' -import type { LiquidationApprovalDetails, LiquidationFundingPreview, ListedSecurityPool, SecurityPoolBrowsePage, SecurityPoolOverviewActionResult, SecurityPoolPage, SecurityPoolVaultSummary } from '../../../types/contracts.js' +import type { LiquidationApprovalDetails, LiquidationFundingPreview, ListedSecurityPool, SecurityPoolBrowsePage, SecurityPoolOverviewActionResult, SecurityPoolPage, SecurityPoolVaultSummary } from '@zoltar/ui-core-shared/types/contracts.js' type UseSecurityPoolsOverviewParameters = { accountAddress: Address | undefined diff --git a/ui/ts/features/security-pools/hooks/useSecurityVaultOperations.ts b/ui/statoblast/ts/features/security-pools/hooks/useSecurityVaultOperations.ts similarity index 94% rename from ui/ts/features/security-pools/hooks/useSecurityVaultOperations.ts rename to ui/statoblast/ts/features/security-pools/hooks/useSecurityVaultOperations.ts index 9fb8e23ff..827f82289 100644 --- a/ui/ts/features/security-pools/hooks/useSecurityVaultOperations.ts +++ b/ui/statoblast/ts/features/security-pools/hooks/useSecurityVaultOperations.ts @@ -1,14 +1,15 @@ import { useSignal } from '@preact/signals' import { useEffect, useRef } from 'preact/hooks' -import { useErc20AllowanceLoader, useErc20BalanceLoader } from '../../../hooks/useErc20Loader.js' -import { useFormState } from '../../../hooks/useFormState.js' -import { useLoadController } from '../../../hooks/useLoadController.js' +import { useErc20AllowanceLoader, useErc20BalanceLoader } from '@zoltar/ui-core-shared/hooks/useErc20Loader.js' +import { useFormState } from '@zoltar/ui-core-shared/hooks/useFormState.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' import type { Address } from '@zoltar/shared/ethereum' -import { addOpenOracleBountyBuffer } from '../../open-oracle/lib/openOracle.js' +import { addOpenOracleBountyBuffer } from '@zoltar/ui-zoltar/features/open-oracle/lib/openOracle.js' import { approveErc20, depositRepToVaultToSecurityPool, loadCoordinatorInitialReportFundingRequirement, + loadErc20Allowance, loadErc20Balance, loadOracleManagerDetails, loadSecurityVaultDetails, @@ -17,24 +18,26 @@ import { redeemSecurityVaultFees, updateSecurityVaultFees, } from '../../../protocol/index.js' -import { assertNever } from '../../../lib/assert.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' -import { normalizeAddress, sameAddress } from '../../../lib/address.js' -import { getErrorMessage, isRecoverableContractReadError } from '../../../lib/errors.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' -import { parseAddressInput } from '../../../lib/inputs.js' -import { getDefaultSecurityVaultFormState, parseBigIntInput, parseRepAmountInput } from '../../markets/lib/marketForm.js' -import { getOracleRequestEthGuardMessage, resolveOracleOperationEthFunding } from '../../open-oracle/lib/oracleRequestEth.js' -import { requireDefined } from '../../../lib/required.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { normalizeAddress, sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { getErrorMessage, isRecoverableContractReadError } from '@zoltar/ui-core-shared/lib/errors.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import { parseAddressInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { parseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { parseRepAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { getDefaultSecurityVaultFormState } from '../../markets/lib/marketForm.js' +import { getOracleRequestEthGuardMessage, resolveOracleOperationEthFunding } from '@zoltar/ui-zoltar/features/open-oracle/lib/oracleRequestEth.js' +import { requireDefined } from '@zoltar/ui-core-shared/lib/required.js' import { doesLoadedSecurityVaultMatchSelection, getSelectedVaultOwner, getStagedOperationTimeoutSeconds, MIN_STAGED_OPERATION_TIMEOUT_MINUTES, parseTargetHealthFactorBps } from '../lib/securityVault.js' import { createSecurityVaultSuccessPresentation, createSecurityVaultTransactionIntent, createSecurityVaultWarningPresentation } from '../../transactionPresentations.js' -import { buildWriteActionConfig, runWriteAction } from '../../../lib/writeAction.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { buildWriteActionConfig, runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { SecurityVaultFormState, WriteOperationsParameters } from '../../../types/app.js' -import type { SecurityVaultActionResult, SecurityVaultDetails } from '../../../types/contracts.js' +import type { SecurityVaultActionResult, SecurityVaultDetails } from '@zoltar/ui-core-shared/types/contracts.js' type UseSecurityVaultOperationsParameters = WriteOperationsParameters & { enabled: boolean @@ -95,8 +98,8 @@ function useSecurityVaultOperationsWithDependencies( const securityVaultMissing = useSignal(false) const securityVaultError = useSignal(undefined) const { state: securityVaultForm, setState: updateSecurityVaultForm } = useFormState(getDefaultSecurityVaultFormState()) - const repBalanceLoader = useErc20BalanceLoader() - const repAllowanceLoader = useErc20AllowanceLoader() + const repBalanceLoader = useErc20BalanceLoader(loadErc20Balance) + const repAllowanceLoader = useErc20AllowanceLoader(loadErc20Allowance) const securityVaultActiveAction = useSignal(undefined) const securityVaultFeedback = useSignal | undefined>(undefined) const securityVaultResult = useSignal(undefined) diff --git a/ui/ts/features/security-pools/hooks/useSelectedVaultWorkflowState.ts b/ui/statoblast/ts/features/security-pools/hooks/useSelectedVaultWorkflowState.ts similarity index 97% rename from ui/ts/features/security-pools/hooks/useSelectedVaultWorkflowState.ts rename to ui/statoblast/ts/features/security-pools/hooks/useSelectedVaultWorkflowState.ts index d962ee1e7..f999a3ba4 100644 --- a/ui/ts/features/security-pools/hooks/useSelectedVaultWorkflowState.ts +++ b/ui/statoblast/ts/features/security-pools/hooks/useSelectedVaultWorkflowState.ts @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { normalizeAddress, sameAddress } from '../../../lib/address.js' +import { normalizeAddress, sameAddress } from '@zoltar/ui-core-shared/lib/address.js' import { isSelectedVaultOwnedByAccount as isSelectedVaultOwnedByAccountHelper } from '../lib/securityVault.js' export type SelectedVaultView = 'browse-vaults' | 'selected-vault' diff --git a/ui/ts/features/security-pools/lib/liquidation.ts b/ui/statoblast/ts/features/security-pools/lib/liquidation.ts similarity index 99% rename from ui/ts/features/security-pools/lib/liquidation.ts rename to ui/statoblast/ts/features/security-pools/lib/liquidation.ts index f64926e41..8ac268a9e 100644 --- a/ui/ts/features/security-pools/lib/liquidation.ts +++ b/ui/statoblast/ts/features/security-pools/lib/liquidation.ts @@ -1,7 +1,7 @@ -import * as liquidationCopy from '../../../copy/liquidation.js' +import * as liquidationCopy from '@zoltar/ui-zoltar/copy/liquidation.js' import { LIQUIDATION_BPS_DENOMINATOR, LIQUIDATION_PRICE_PRECISION, LIQUIDATION_REP_BONUS_BPS, getLiquidationVaultRepBackingToTransfer, getMaximumFundedDebtAttoEth } from '@zoltar/shared/liquidation' import { DEFAULT_PROTOCOL_CONFIG } from '@zoltar/shared/protocolConfig' -import type { SecurityPoolVaultSummary } from '../../../types/contracts.js' +import type { SecurityPoolVaultSummary } from '@zoltar/ui-core-shared/types/contracts.js' const DEFAULT_MINIMUM_SECURITY_BOND_DEBT_ATTO_ETH = DEFAULT_PROTOCOL_CONFIG.minimumSecurityBondDebtAttoEth const DEFAULT_MINIMUM_VAULT_REP_DEPOSIT_ATTO_REP = 10n * 10n ** 18n diff --git a/ui/ts/features/security-pools/lib/liquidationStatus.ts b/ui/statoblast/ts/features/security-pools/lib/liquidationStatus.ts similarity index 92% rename from ui/ts/features/security-pools/lib/liquidationStatus.ts rename to ui/statoblast/ts/features/security-pools/lib/liquidationStatus.ts index 8e6f0a57b..493667e9b 100644 --- a/ui/ts/features/security-pools/lib/liquidationStatus.ts +++ b/ui/statoblast/ts/features/security-pools/lib/liquidationStatus.ts @@ -1,6 +1,6 @@ -import { sameAddress } from '../../../lib/address.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' import { isOracleManagerPriceUsable } from './securityVault.js' -import type { OracleManagerDetails, SecurityPoolOverviewActionResult } from '../../../types/contracts.js' +import type { OracleManagerDetails, SecurityPoolOverviewActionResult } from '@zoltar/ui-core-shared/types/contracts.js' type LiquidationNoticeState = 'failed' | 'queued' | 'submitted' | 'successful' diff --git a/ui/ts/features/security-pools/lib/retentionRate.ts b/ui/statoblast/ts/features/security-pools/lib/retentionRate.ts similarity index 100% rename from ui/ts/features/security-pools/lib/retentionRate.ts rename to ui/statoblast/ts/features/security-pools/lib/retentionRate.ts diff --git a/ui/ts/features/security-pools/lib/securityPoolCreationGuards.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolCreationGuards.ts similarity index 93% rename from ui/ts/features/security-pools/lib/securityPoolCreationGuards.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolCreationGuards.ts index 80fc68e2f..2713d365c 100644 --- a/ui/ts/features/security-pools/lib/securityPoolCreationGuards.ts +++ b/ui/statoblast/ts/features/security-pools/lib/securityPoolCreationGuards.ts @@ -1,8 +1,8 @@ import type { Address } from '@zoltar/shared/ethereum' import { MAX_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS } from '@zoltar/shared/oracleInitialReport' -import type { MarketDetails } from '../../../types/contracts.js' -import { getWalletActiveAppChainGuardState } from '../../../lib/actionGuards.js' -import { tryParseDecimalInput } from '../../../lib/decimal.js' +import type { MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWalletActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' +import { tryParseDecimalInput } from '@zoltar/ui-core-shared/lib/decimal.js' import { tryParseStatoblastSecurityMultiplierBpsInput } from '../../markets/lib/marketForm.js' export function getStatoblastSecurityMultiplierValidationMessage(statoblastSecurityMultiplier: string) { diff --git a/ui/ts/features/security-pools/lib/securityPoolLabels.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolLabels.ts similarity index 89% rename from ui/ts/features/security-pools/lib/securityPoolLabels.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolLabels.ts index 31075b87e..981cb5989 100644 --- a/ui/ts/features/security-pools/lib/securityPoolLabels.ts +++ b/ui/statoblast/ts/features/security-pools/lib/securityPoolLabels.ts @@ -1,8 +1,8 @@ -import * as securityPoolCopy from '../../../copy/securityPool.js' -import { assertNever } from '../../../lib/assert.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' import type { SecurityPoolLifecycleState } from './securityPoolState.js' -import { getReportingOutcomeLabel } from '../../reporting/lib/reporting.js' -import type { ReportingOutcomeKey } from '../../../types/contracts.js' +import { getReportingOutcomeLabel } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' +import type { ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' type VaultLauncherAction = 'claim-fees' | 'deposit-rep' | 'rep-exit' type RepExitMode = 'redeem' | 'withdraw' diff --git a/ui/ts/features/security-pools/lib/securityPoolNavigation.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolNavigation.ts similarity index 78% rename from ui/ts/features/security-pools/lib/securityPoolNavigation.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolNavigation.ts index 52752e45a..3f7d561f2 100644 --- a/ui/ts/features/security-pools/lib/securityPoolNavigation.ts +++ b/ui/statoblast/ts/features/security-pools/lib/securityPoolNavigation.ts @@ -1,12 +1,5 @@ -import { buildRouteHref, getRouteHashSearch, getTopLevelRouteSearch, SECURITY_POOLS_ROUTE } from '../../../lib/routing.js' -import { readSelectedPoolViewQueryParam, readUniverseQueryParam, writeSecurityPoolQueryParam, writeSecurityPoolQuestionIdQueryParam, writeSelectedPoolViewQueryParam, writeUniverseQueryParam } from '../../../lib/urlParams.js' - -export function getUseQuestionForPoolState(questionId: string) { - return { - marketId: questionId, - securityPoolAddress: '', - } -} +import { buildRouteHref, getRouteHash, getRouteHashSearch, getTopLevelRouteSearch } from '@zoltar/ui-core-shared/lib/routing.js' +import { readSelectedPoolViewQueryParam, readUniverseQueryParam, writeSecurityPoolQueryParam, writeSecurityPoolQuestionIdQueryParam, writeSelectedPoolViewQueryParam, writeUniverseQueryParam } from '@zoltar/ui-core-shared/lib/urlParams.js' export function getSecurityPoolLinkHref(securityPoolAddress: string, selectedPoolView?: string, universeId?: bigint) { const currentSearch = getRouteHashSearch() @@ -15,14 +8,14 @@ export function getSecurityPoolLinkHref(securityPoolAddress: string, selectedPoo const securityPoolSearch = writeSecurityPoolQueryParam(currentSearch, securityPoolAddress) const selectedPoolViewSearch = writeSelectedPoolViewQueryParam(securityPoolSearch, nextSelectedPoolView) const nextSearch = writeUniverseQueryParam(selectedPoolViewSearch, nextUniverseId) - return buildRouteHref(SECURITY_POOLS_ROUTE, nextSearch) + return buildRouteHref(getRouteHash('security-pools'), nextSearch) } export function getUseQuestionForPoolHref(questionId: string, universeId?: bigint) { const currentSearch = getTopLevelRouteSearch('security-pools') const questionSearch = writeSecurityPoolQuestionIdQueryParam(currentSearch, questionId) const nextSearch = writeUniverseQueryParam(questionSearch, universeId ?? readUniverseQueryParam(currentSearch)) - return buildRouteHref(SECURITY_POOLS_ROUTE, nextSearch) + return buildRouteHref(getRouteHash('security-pools'), nextSearch) } export function navigateToSecurityPool(securityPoolAddress: string, selectedPoolView?: string, universeId?: bigint) { diff --git a/ui/ts/features/security-pools/lib/securityPoolReadiness.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolReadiness.ts similarity index 100% rename from ui/ts/features/security-pools/lib/securityPoolReadiness.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolReadiness.ts diff --git a/ui/ts/features/security-pools/lib/securityPoolState.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolState.ts similarity index 100% rename from ui/ts/features/security-pools/lib/securityPoolState.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolState.ts diff --git a/ui/ts/features/security-pools/lib/securityPoolState/axes.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolState/axes.ts similarity index 92% rename from ui/ts/features/security-pools/lib/securityPoolState/axes.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolState/axes.ts index f399bed5a..6bd52eda4 100644 --- a/ui/ts/features/security-pools/lib/securityPoolState/axes.ts +++ b/ui/statoblast/ts/features/security-pools/lib/securityPoolState/axes.ts @@ -1,8 +1,8 @@ -import { assertNever } from '../../../../lib/assert.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' import type { ForkAuctionStageView } from '../../../truth-auctions/lib/forkAuction.js' -import { getEscalationPhase, isPoolQuestionFinalized } from '../../../reporting/lib/reportingDomain.js' +import { getEscalationPhase, isPoolQuestionFinalized } from '@zoltar/ui-zoltar/features/reporting/lib/reportingDomain.js' import type { SecurityPoolForkStage, SecurityPoolLifecycleState, SecurityPoolReportingStage } from './types.js' -import type { ReportingDetails, ReportingOutcomeKey, SecurityPoolSystemState } from '../../../../types/contracts.js' +import type { ReportingDetails, ReportingOutcomeKey, SecurityPoolSystemState } from '@zoltar/ui-core-shared/types/contracts.js' export function isSecurityPoolEnded({ hasForkActivity, diff --git a/ui/ts/features/security-pools/lib/securityPoolState/engine.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolState/engine.ts similarity index 100% rename from ui/ts/features/security-pools/lib/securityPoolState/engine.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolState/engine.ts diff --git a/ui/ts/features/security-pools/lib/securityPoolState/matrix.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolState/matrix.ts similarity index 100% rename from ui/ts/features/security-pools/lib/securityPoolState/matrix.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolState/matrix.ts diff --git a/ui/ts/features/security-pools/lib/securityPoolState/types.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolState/types.ts similarity index 100% rename from ui/ts/features/security-pools/lib/securityPoolState/types.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolState/types.ts diff --git a/ui/ts/features/security-pools/lib/securityPoolWorkflow.ts b/ui/statoblast/ts/features/security-pools/lib/securityPoolWorkflow.ts similarity index 88% rename from ui/ts/features/security-pools/lib/securityPoolWorkflow.ts rename to ui/statoblast/ts/features/security-pools/lib/securityPoolWorkflow.ts index ce6da1451..cc7ec557b 100644 --- a/ui/ts/features/security-pools/lib/securityPoolWorkflow.ts +++ b/ui/statoblast/ts/features/security-pools/lib/securityPoolWorkflow.ts @@ -1,15 +1,12 @@ -import * as commonCopy from '../../../copy/common.js' -import * as securityPoolCopy from '../../../copy/securityPool.js' -import { sameAddress } from '../../../lib/address.js' -import { assertNever } from '../../../lib/assert.js' -import { formatDuration, formatRoundedCurrencyBalance } from '../../../lib/formatters.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' import { deriveHasForkActivity, getForkAuctionStageView, type ForkAuctionStageView } from '../../truth-auctions/lib/forkAuction.js' -import type { LoadableValueState } from '../../../lib/loadState.js' -import { getOracleManagerPriceValidUntilTimestamp } from './securityVault.js' -import { getTimeRemaining } from '../../../lib/time.js' -import type { UserMessagePresentation } from '../../../lib/userCopy.js' -import { resolveEnumValue } from '../../../lib/viewState.js' -import type { ListedSecurityPool, OracleManagerDetails, ReportingDetails, ReportingOutcomeKey, SecurityPoolSystemState, TruthAuctionMetrics } from '../../../types/contracts.js' +import type { LoadableValueState } from '@zoltar/ui-core-shared/lib/loadState.js' +import type { UserMessagePresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import { resolveEnumValue } from '@zoltar/ui-core-shared/lib/viewState.js' +import type { ListedSecurityPool, OracleManagerDetails, ReportingDetails, ReportingOutcomeKey, SecurityPoolSystemState, TruthAuctionMetrics } from '@zoltar/ui-core-shared/types/contracts.js' const FORK_WORKFLOW_SELECTION_STAGES = ['fork-triggered', 'migration', 'auction', 'settlement'] as const export type ForkWorkflowSelectionStage = (typeof FORK_WORKFLOW_SELECTION_STAGES)[number] @@ -383,22 +380,6 @@ export function getSelectedPoolWorkflowLockedPresentation({ hasSelectedPoolAddre export function isForkWorkflowDisabled(selectedPoolState: SecurityPoolSystemState | undefined, selectedPoolHasForkActivity = false) { return selectedPoolState === undefined || (selectedPoolState === 'operational' && !selectedPoolHasForkActivity) } -export function getOracleLastPriceDisplay({ lastPrice, lastSettlementTimestamp }: { lastPrice: bigint; lastSettlementTimestamp: bigint }) { - if (lastSettlementTimestamp === 0n) return '-' - return `≈ ${formatRoundedCurrencyBalance(lastPrice, 18, 2)}\u00a0REP / ETH` -} -export function getOraclePriceValidityPresentation({ currentTimestamp, lastSettlementTimestamp, priceValidUntilTimestamp }: { currentTimestamp: bigint; lastSettlementTimestamp: bigint; priceValidUntilTimestamp: bigint | undefined }) { - if (lastSettlementTimestamp === 0n) return undefined - const validUntilTimestamp = priceValidUntilTimestamp ?? getOracleManagerPriceValidUntilTimestamp(lastSettlementTimestamp) - if (validUntilTimestamp === undefined) return undefined - const timeRemaining = getTimeRemaining(validUntilTimestamp, currentTimestamp) - if (timeRemaining === undefined) return undefined - if (timeRemaining === 0n) { - const expiredFor = currentTimestamp > validUntilTimestamp ? currentTimestamp - validUntilTimestamp : 0n - return { text: `(expired ${expiredFor === 0n ? 'less than a minute' : formatDuration(expiredFor)} ago)`, tone: 'danger' as const } - } - return { text: `(Valid for ${formatDuration(timeRemaining)})`, tone: 'success' as const } -} export function getCurrentPoolOracleManagerDetails({ poolOracleManagerDetails, selectedPoolManagerAddress }: { poolOracleManagerDetails: OracleManagerDetails | undefined; selectedPoolManagerAddress: string | undefined }) { if (!sameAddress(poolOracleManagerDetails?.managerAddress, selectedPoolManagerAddress)) return undefined return poolOracleManagerDetails diff --git a/ui/ts/features/security-pools/lib/securityVault.ts b/ui/statoblast/ts/features/security-pools/lib/securityVault.ts similarity index 97% rename from ui/ts/features/security-pools/lib/securityVault.ts rename to ui/statoblast/ts/features/security-pools/lib/securityVault.ts index bd1171ecc..22bcf93ac 100644 --- a/ui/ts/features/security-pools/lib/securityVault.ts +++ b/ui/statoblast/ts/features/security-pools/lib/securityVault.ts @@ -1,8 +1,8 @@ import type { Address } from '@zoltar/shared/ethereum' -import type { OracleManagerDetails } from '../../../types/contracts.js' -import type { SecurityVaultDetails } from '../../../types/contracts.js' -import { sameAddress } from '../../../lib/address.js' -import { getOracleManagerPriceValidUntilTimestamp, ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS } from '../../../protocol/oracleTiming.js' +import type { OracleManagerDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import type { SecurityVaultDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { getOracleManagerPriceValidUntilTimestamp, ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS } from '@zoltar/ui-zoltar/protocol/oracleTiming.js' export { getOracleManagerPriceValidUntilTimestamp, ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS } diff --git a/ui/ts/features/security-pools/lib/securityVaultGuards.ts b/ui/statoblast/ts/features/security-pools/lib/securityVaultGuards.ts similarity index 95% rename from ui/ts/features/security-pools/lib/securityVaultGuards.ts rename to ui/statoblast/ts/features/security-pools/lib/securityVaultGuards.ts index fb5ea0d3d..b4ba29154 100644 --- a/ui/ts/features/security-pools/lib/securityVaultGuards.ts +++ b/ui/statoblast/ts/features/security-pools/lib/securityVaultGuards.ts @@ -1,7 +1,7 @@ import type { Address } from '@zoltar/shared/ethereum' -import { getWalletActiveAppChainGuardState } from '../../../lib/actionGuards.js' -import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' -import { getOracleRequestEthGuardMessage } from '../../open-oracle/lib/oracleRequestEth.js' +import { getWalletActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' +import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { getOracleRequestEthGuardMessage } from '@zoltar/ui-zoltar/features/open-oracle/lib/oracleRequestEth.js' import { MAX_STAGED_OPERATION_TIMEOUT_MINUTES, MIN_SECURITY_VAULT_REP_DEPOSIT_ATTO_REP, MIN_STAGED_OPERATION_TIMEOUT_MINUTES, parseTargetHealthFactorBps } from './securityVault.js' export function getTargetHealthFactorGuardMessage(targetHealthFactor: string) { diff --git a/ui/ts/features/security-pools/lib/securityVaultHandlers.ts b/ui/statoblast/ts/features/security-pools/lib/securityVaultHandlers.ts similarity index 100% rename from ui/ts/features/security-pools/lib/securityVaultHandlers.ts rename to ui/statoblast/ts/features/security-pools/lib/securityVaultHandlers.ts diff --git a/ui/statoblast/ts/features/transactionPresentations.tsx b/ui/statoblast/ts/features/transactionPresentations.tsx new file mode 100644 index 000000000..d0739682d --- /dev/null +++ b/ui/statoblast/ts/features/transactionPresentations.tsx @@ -0,0 +1,296 @@ +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as transactionCopy from '@zoltar/ui-core-shared/copy/transaction.js' +import * as securityPoolCopy from '@zoltar/ui-zoltar/copy/securityPool.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { IdentifierValue } from '@zoltar/ui-core-shared/components/IdentifierValue.js' +import { formatCurrencyBalanceWithUnit, formatValueWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { getReportingOutcomeLabel } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' +import { buildIntent, buildPresentation, getPoolUniverseTransactionRows, humanizeTransactionAction, withWarning } from '@zoltar/ui-core-shared/lib/transactionPresentations.js' +import type { PoolUniverseTransactionContext } from '@zoltar/ui-core-shared/lib/transactionPresentations.js' +import type { TransactionIntent } from '@zoltar/ui-core-shared/types/components.js' +import type { ForkAuctionActionResult, ReportingActionResult, SecurityPoolCreationResult, SecurityPoolOverviewActionResult, SecurityVaultActionResult, TradingActionResult } from '@zoltar/ui-core-shared/types/contracts.js' +import { AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL } from './truth-auctions/lib/forkAuction.js' +import { formatStatoblastSecurityMultiplier } from './markets/lib/trading.js' + +type SecurityPoolCreationTransactionContext = { + initialReportPriorityFeeGwei?: string | undefined + questionId?: string | undefined + statoblastSecurityMultiplierBps?: bigint | undefined + universeId?: bigint | undefined +} + +function getSecurityPoolCreationTransactionRows(context: SecurityPoolCreationTransactionContext | undefined) { + if (context === undefined) return undefined + return [ + ...(context.initialReportPriorityFeeGwei === undefined || context.initialReportPriorityFeeGwei.trim() === '' ? [] : [{ label: commonCopy.initialReportPriorityFee, value: formatValueWithUnit(context.initialReportPriorityFeeGwei.trim(), commonCopy.gwei) }]), + ...(context.questionId === undefined || context.questionId.trim() === '' ? [] : [{ label: commonCopy.questionId, value: }]), + ...(context.statoblastSecurityMultiplierBps === undefined ? [] : [{ label: commonCopy.statoblastSecurityMultiplierBps, value: `${formatStatoblastSecurityMultiplier(context.statoblastSecurityMultiplierBps)}x` }]), + ] +} + +export function createSecurityPoolCreationTransactionIntent(context?: SecurityPoolCreationTransactionContext) { + return buildIntent({ + action: 'createSecurityPool', + rows: getSecurityPoolCreationTransactionRows(context), + source: 'security-pools', + submittedTitle: transactionCopy.creatingSecurityPool, + universeId: context?.universeId, + }) +} + +export function createSecurityPoolCreationSuccessPresentation(result: SecurityPoolCreationResult) { + return buildPresentation({ + detail: transactionCopy.securityPoolCreatedDetail, + hash: result.deployPoolHash, + rows: [ + { label: transactionCopy.pool, value: }, + { label: commonCopy.questionId, value: }, + { label: commonCopy.statoblastSecurityMultiplierBps, value: `${formatStatoblastSecurityMultiplier(result.statoblastSecurityMultiplierBps)}x` }, + { label: commonCopy.initialReportPriorityFee, value: formatCurrencyBalanceWithUnit(result.initialReportPriorityFeeAttoEthPerGas, commonCopy.gwei, 9) }, + ], + title: transactionCopy.securityPoolCreated, + tone: 'success', + universeId: result.universeId, + }) +} + +export function createSecurityPoolCreationWarningPresentation(result: SecurityPoolCreationResult, message: string) { + return withWarning(createSecurityPoolCreationSuccessPresentation(result), message) +} + +type SecurityVaultTransactionContext = { + securityPoolAddress?: string | undefined + universeId?: bigint | undefined + vaultAddress?: string | undefined +} + +function getSecurityVaultTransactionRows(context: SecurityVaultTransactionContext | undefined) { + if (context === undefined) return undefined + return [ + ...(context.securityPoolAddress === undefined || context.securityPoolAddress.trim() === '' ? [] : [{ label: commonCopy.securityPoolAddress, value: }]), + ...(context.vaultAddress === undefined || context.vaultAddress.trim() === '' ? [] : [{ label: securityPoolCopy.vault, value: }]), + ] +} + +function getSecurityVaultActionTitle(actionName: SecurityVaultActionResult['action']) { + if (actionName === 'depositRepToVault') return securityPoolCopy.depositRepToVault + if (actionName === 'queueWithdrawRep') return securityPoolCopy.withdrawRep + return humanizeTransactionAction(actionName) +} + +export function createSecurityVaultTransactionIntent(actionName: SecurityVaultActionResult['action'], context?: SecurityVaultTransactionContext) { + return buildIntent({ + action: actionName, + rows: getSecurityVaultTransactionRows(context), + source: 'security-vault', + submittedTitle: getSecurityVaultActionTitle(actionName), + universeId: context?.universeId, + }) +} + +export function createSecurityVaultSuccessPresentation(result: SecurityVaultActionResult, context?: SecurityVaultTransactionContext) { + let queuedOperationDetail: string | undefined + if (result.queuedOperation !== undefined) { + queuedOperationDetail = result.queuedOperation.isPendingSlot ? transactionCopy.formatQueuedOperationAutoExecutionDetail(result.queuedOperation.operationId.toString()) : transactionCopy.formatQueuedOperationManualExecutionDetail(result.queuedOperation.operationId.toString()) + } + return buildPresentation({ + ...(queuedOperationDetail === undefined ? {} : { detail: queuedOperationDetail }), + hash: result.hash, + rows: [...(getSecurityVaultTransactionRows(context) ?? []), ...(result.queuedOperation === undefined ? [] : [{ label: commonCopy.stagedOperation, value: `#${result.queuedOperation.operationId.toString()}` }])], + title: getSecurityVaultActionTitle(result.action), + tone: 'success', + universeId: context?.universeId, + }) +} + +export function createSecurityVaultWarningPresentation(result: SecurityVaultActionResult, message: string, context?: SecurityVaultTransactionContext) { + return withWarning(createSecurityVaultSuccessPresentation(result, context), message) +} + +type TradingTransactionContext = PoolUniverseTransactionContext & { + shareOutcome?: ReportingActionResult['outcome'] | undefined +} + +function getTradingTransactionRows(context: TradingTransactionContext | undefined) { + return [...(getPoolUniverseTransactionRows(context) ?? []), ...(context?.shareOutcome === undefined ? [] : [{ identityKey: 'outcome', label: transactionCopy.shareOutcome, value: getReportingOutcomeLabel(context.shareOutcome) }])] +} + +export function createTradingTransactionIntent(actionName: TradingActionResult['action'], context?: TradingTransactionContext) { + return buildIntent({ + action: actionName, + rows: getTradingTransactionRows(context), + source: 'trading', + submittedTitle: humanizeTransactionAction(actionName), + universeId: context?.universeId, + }) +} + +export function createTradingSuccessPresentation(result: TradingActionResult) { + const detail = (() => { + if (result.action === 'createCompleteSet') return undefined + if (result.action === 'redeemCompleteSet') return transactionCopy.completeSetBurnSuccessDetail + if (result.action === 'migrateShares') return transactionCopy.parentPoolSharesMigratedDetail + return undefined + })() + return buildPresentation({ + ...(detail === undefined ? {} : { detail }), + hash: result.hash, + rows: [ + { identityKey: 'security-pool', label: transactionCopy.pool, value: }, + ...(result.shareOutcome === undefined ? [] : [{ identityKey: 'outcome', label: transactionCopy.shareOutcome, value: getReportingOutcomeLabel(result.shareOutcome) }]), + ...(result.targetOutcomeIndexes === undefined ? [] : [{ label: transactionCopy.targetOutcomeIndexes, value: result.targetOutcomeIndexes.join(', ') }]), + ], + title: humanizeTransactionAction(result.action), + tone: 'success', + universeId: result.universeId, + }) +} + +export function createTradingWarningPresentation(result: TradingActionResult, message: string) { + return withWarning(createTradingSuccessPresentation(result), message) +} + +type LiquidationTransactionContext = PoolUniverseTransactionContext & { + amount?: string | undefined + targetVault?: string | undefined +} + +function getLiquidationTransactionRows(context: LiquidationTransactionContext | undefined) { + return [ + ...(getPoolUniverseTransactionRows(context) ?? []), + ...(context?.targetVault === undefined || context.targetVault.trim() === '' ? [] : [{ label: commonCopy.targetVault, value: }]), + ...(context?.amount === undefined || context.amount.trim() === '' ? [] : [{ label: securityPoolCopy.requestedLiquidationDebt, value: formatValueWithUnit(context.amount.trim(), commonCopy.eth) }]), + ] +} + +export function createLiquidationTransactionIntent(context?: LiquidationTransactionContext) { + return buildIntent({ + action: 'queueLiquidation', + rows: getLiquidationTransactionRows(context), + source: 'security-pools', + submittedTitle: transactionCopy.submittingLiquidation, + universeId: context?.universeId, + }) +} + +export function createLiquidationSuccessPresentation(result: SecurityPoolOverviewActionResult, context?: LiquidationTransactionContext) { + let queuedOperationDetail: string = transactionCopy.liquidationRequestSubmittedDetail + if (result.queuedOperation !== undefined) { + queuedOperationDetail = result.queuedOperation.isPendingSlot ? transactionCopy.formatQueuedLiquidationAutoExecutionDetail(result.queuedOperation.operationId.toString()) : transactionCopy.formatQueuedLiquidationManualExecutionDetail(result.queuedOperation.operationId.toString()) + } + return buildPresentation({ + detail: result.stagedExecution?.success === true ? transactionCopy.liquidationExecutedImmediatelyDetail : queuedOperationDetail, + hash: result.hash, + rows: [...getLiquidationTransactionRows({ ...context, securityPoolAddress: result.securityPoolAddress }), ...(result.queuedOperation === undefined ? [] : [{ label: commonCopy.stagedOperation, value: `#${result.queuedOperation.operationId.toString()}` }])], + title: result.stagedExecution?.success === true ? commonCopy.liquidationExecuted : commonCopy.liquidationSubmitted, + tone: 'success', + universeId: context?.universeId, + }) +} + +export function createLiquidationFailurePresentation(result: SecurityPoolOverviewActionResult, detail: string, context?: LiquidationTransactionContext) { + return buildPresentation({ + detail, + hash: result.hash, + rows: [...getLiquidationTransactionRows({ ...context, securityPoolAddress: result.securityPoolAddress }), ...(result.stagedExecution === undefined ? [] : [{ label: commonCopy.stagedOperation, value: `#${result.stagedExecution.operationId.toString()}` }])], + title: commonCopy.liquidationFailed, + tone: 'error', + universeId: context?.universeId, + }) +} + +export function createLiquidationWarningPresentation(result: SecurityPoolOverviewActionResult, message: string, context?: LiquidationTransactionContext) { + return withWarning(createLiquidationSuccessPresentation(result, context), message) +} +export function createForkAuctionTransactionIntent(actionName: ForkAuctionActionResult['action'], { context, submittedTitle }: { context?: PoolUniverseTransactionContext; submittedTitle?: TransactionIntent['submittedTitle'] } = {}) { + let resolvedSubmittedTitle = submittedTitle + if (resolvedSubmittedTitle === undefined) { + if (actionName === 'migrateUnresolvedEscalation') { + resolvedSubmittedTitle = transactionCopy.clearUnresolvedParentEscalationDepositAccounting + } else if (actionName === 'claimParentEscalationDeposits') { + resolvedSubmittedTitle = transactionCopy.claimParentEscalationDeposits + } else { + resolvedSubmittedTitle = humanizeTransactionAction(actionName) + } + } + return buildIntent({ + action: actionName, + rows: getPoolUniverseTransactionRows(context), + source: 'fork-auction', + submittedTitle: resolvedSubmittedTitle, + universeId: context?.universeId, + }) +} + +export function createForkAuctionSuccessPresentation(result: ForkAuctionActionResult) { + let title = humanizeTransactionAction(result.action) + if (result.action === 'claimAuctionProceeds' && result.settlementMode === 'refund') { + title = transactionCopy.settleFinalizedRefunds + } else if (result.action === 'migrateUnresolvedEscalation') { + title = transactionCopy.clearUnresolvedParentEscalationDepositAccounting + } else if (result.action === 'claimParentEscalationDeposits') { + title = transactionCopy.claimParentEscalationDeposits + } + const detail = (() => { + switch (result.action) { + case 'claimAuctionProceeds': + if (result.settlementMode === 'refund') { + return transactionCopy.formatFinalizedRefundSettlementResultDetail(AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL) + } + if (result.settlementMode === 'claim') { + return transactionCopy.formatWinningBidSettlementResultDetail(AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL) + } + return transactionCopy.formatMixedBidSettlementResultDetail(AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL) + case 'createChildUniverse': + return transactionCopy.childUniverseLinkedToForkPathDetail + case 'forkWithOwnEscalation': + return transactionCopy.ownEscalationForkSubmittedDetail + case 'forkUniverse': + return transactionCopy.zoltarUniverseForkSubmittedDetail + case 'initiateFork': + return transactionCopy.poolReadyForForkMigrationDetail + case 'claimParentEscalationDeposits': + return transactionCopy.parentEscalationDepositsClaimedDetail + case 'migrateRepToZoltar': + return transactionCopy.poolRepMigrationSuccessDetail + case 'migrateUnresolvedEscalation': + return transactionCopy.unresolvedEscalationMigratedDetail + case 'migrateVault': + return transactionCopy.vaultMigratedDetail + case 'refundLosingBids': + return transactionCopy.losingBidsRefundedDetail + case 'settleForkedEscalation': + return transactionCopy.forkDepositSettlementSuccessDetail + case 'startTruthAuction': + return transactionCopy.truthAuctionStartedSuccessDetail + case 'submitBid': + return transactionCopy.truthAuctionBidSuccessDetail + default: + return undefined + } + })() + return buildPresentation({ + ...(detail === undefined ? {} : { detail }), + hash: result.hash, + rows: [{ label: transactionCopy.pool, value: }], + title, + tone: 'success', + universeId: result.universeId, + }) +} + +export function createForkAuctionWarningPresentation(result: ForkAuctionActionResult, message: string) { + return withWarning(createForkAuctionSuccessPresentation(result), message) +} + +export { + createMarketCreationSuccessPresentation, + createMarketCreationTransactionIntent, + createMarketCreationWarningPresentation, + createOpenOracleSuccessPresentation, + createOpenOracleTransactionIntent, + createPoolOracleSuccessPresentation, + createPoolOracleTransactionIntent, + createReportingSuccessPresentation, + createReportingTransactionIntent, +} from '@zoltar/ui-zoltar/features/transactionPresentations.js' diff --git a/ui/ts/features/truth-auctions/components/ForkAuctionSection.tsx b/ui/statoblast/ts/features/truth-auctions/components/ForkAuctionSection.tsx similarity index 97% rename from ui/ts/features/truth-auctions/components/ForkAuctionSection.tsx rename to ui/statoblast/ts/features/truth-auctions/components/ForkAuctionSection.tsx index 249513c19..5ac09ef86 100644 --- a/ui/ts/features/truth-auctions/components/ForkAuctionSection.tsx +++ b/ui/statoblast/ts/features/truth-auctions/components/ForkAuctionSection.tsx @@ -1,54 +1,54 @@ -import * as commonCopy from '../../../copy/common.js' -import * as forkAuctionCopy from '../../../copy/forkAuction.js' -import * as transactionReviewCopy from '../../../copy/transactionReview.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as forkAuctionCopy from '@zoltar/ui-zoltar/copy/forkAuction.js' +import * as transactionReviewCopy from '@zoltar/ui-core-shared/copy/transactionReview.js' import { Fragment } from 'preact' import { useEffect, useState } from 'preact/hooks' import type { ComponentChildren } from 'preact' import { zeroAddress } from '@zoltar/shared/ethereum' -import { AddressValue } from '../../../components/AddressValue.js' -import { Badge } from '../../../components/Badge.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { EscalationDepositSelectionList } from '../../reporting/components/EscalationDepositSelectionList.js' -import { EnumDropdown } from '../../../components/EnumDropdown.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { ImportedForkSettlementSection } from '../../reporting/components/ImportedForkSettlementSection.js' -import { LookupFieldRow } from '../../../components/LookupFieldRow.js' -import { LoadingAwareText, LoadingText } from '../../../components/LoadingText.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' -import { ReadOnlyDetailAccordion } from '../../../components/ReadOnlyDetailAccordion.js' -import { RouteWorkflowPanel } from '../../../components/RouteWorkflowPanel.js' -import { SectionBlock } from '../../../components/SectionBlock.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { EscalationDepositSelectionList } from '@zoltar/ui-zoltar/features/reporting/components/EscalationDepositSelectionList.js' +import { EnumDropdown } from '@zoltar/ui-core-shared/components/EnumDropdown.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { ImportedForkSettlementSection } from '@zoltar/ui-zoltar/features/reporting/components/ImportedForkSettlementSection.js' +import { LookupFieldRow } from '@zoltar/ui-core-shared/components/LookupFieldRow.js' +import { LoadingAwareText, LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { ReadOnlyDetailAccordion } from '@zoltar/ui-core-shared/components/ReadOnlyDetailAccordion.js' +import { RouteWorkflowPanel } from '@zoltar/ui-core-shared/components/RouteWorkflowPanel.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' import { SecurityPoolLink } from '../../security-pools/components/SecurityPoolLink.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { TimestampValue } from '../../../components/TimestampValue.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' import { TruthAuctionBidsSection, ViewerTruthAuctionBidsSection } from './TruthAuctionBidsSection.js' import { TruthAuctionMarketViewSection } from './TruthAuctionMarketViewSection.js' import { TruthAuctionSummaryCard } from './TruthAuctionSummaryCard.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { createActionAvailability } from '../../../lib/actionAvailability.js' -import { sameAddress } from '../../../lib/address.js' -import { assertNever } from '../../../lib/assert.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' +import { createActionAvailability } from '@zoltar/ui-core-shared/lib/actionAvailability.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' import { AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL, AUCTION_TIME_SECONDS, getForkAuctionStageLabel, getForkAuctionStageView, getTimeRemaining } from '../lib/forkAuction.js' import { buildTruthAuctionDepthPoints, estimateRepPurchased, getTruthAuctionBidGuardMessage, getTruthAuctionBidPreview, getTruthAuctionBidPriceValidationMessage, getTruthAuctionOverviewProgress, getTruthAuctionWinningThresholdPrice } from '../lib/truthAuctionBook.js' import { buildTruthAuctionBidRows, buildViewerTruthAuctionBidRows, updateTruthAuctionSettlementBidSelection } from '../lib/truthAuctionBidViewModels.js' import { getTruthAuctionSettlementAction } from '../lib/truthAuctionSettlementActionState.js' import { getTruthAuctionSettlementActionAvailabilityMessage, getTruthAuctionSettlementBidRows, getTruthAuctionSettlementSelectionEstimate } from '../lib/truthAuctionSettlement.js' -import { formatCurrencyInputBalance, formatDuration, formatRoundedCurrencyBalance } from '../../../lib/formatters.js' -import { tryParseTruthAuctionAmountInput } from '../../markets/lib/marketForm.js' -import { getWrongNetworkMessage, isActiveAppChain } from '../../../lib/network.js' -import { REPORTING_OUTCOME_DROPDOWN_OPTIONS, getReportingOutcomeLabel } from '../../reporting/lib/reporting.js' -import { getEscalationDepositClaimAmount, isPoolQuestionFinalized } from '../../reporting/lib/reportingDomain.js' +import { formatCurrencyInputBalance, formatDuration, formatRoundedCurrencyBalance } from '@zoltar/ui-core-shared/lib/formatters.js' +import { tryParseTruthAuctionAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { getWrongNetworkMessage, isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { REPORTING_OUTCOME_DROPDOWN_OPTIONS, getReportingOutcomeLabel } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' +import { getEscalationDepositClaimAmount, isPoolQuestionFinalized } from '@zoltar/ui-zoltar/features/reporting/lib/reportingDomain.js' import { deriveSecurityPoolForkStage, deriveSecurityPoolLifecycleState, evaluateSecurityPoolState } from '../../security-pools/lib/securityPoolState.js' import { getCurrentSelectedPoolForkAuctionDetails, getForkWorkflowStageSelection, type ForkWorkflowSelectionStage } from '../../security-pools/lib/securityPoolWorkflow.js' -import { getVisualRatio } from '../../../lib/visualMetrics.js' +import { getVisualRatio } from '@zoltar/ui-core-shared/lib/visualMetrics.js' import { useForkAuctionInteractionState } from '../hooks/useForkAuctionInteractionState.js' import { useSelectedAuctionReadState } from '../hooks/useSelectedAuctionReadState.js' import { useTruthAuctionBookData } from '../hooks/useTruthAuctionBookData.js' import { useTruthAuctionSettlementActionState } from '../hooks/useTruthAuctionSettlementActionState.js' -import type { ListedSecurityPool, ReadClient, ReportingOutcomeKey, TruthAuctionMetrics } from '../../../types/contracts.js' +import type { ListedSecurityPool, ReadClient, ReportingOutcomeKey, TruthAuctionMetrics } from '@zoltar/ui-core-shared/types/contracts.js' import type { ForkAuctionSectionProps } from '../../types.js' function sameBigIntArray(left: bigint[], right: bigint[]) { diff --git a/ui/ts/features/truth-auctions/components/TruthAuctionBidsSection.tsx b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionBidsSection.tsx similarity index 92% rename from ui/ts/features/truth-auctions/components/TruthAuctionBidsSection.tsx rename to ui/statoblast/ts/features/truth-auctions/components/TruthAuctionBidsSection.tsx index 4320858f2..c54634087 100644 --- a/ui/ts/features/truth-auctions/components/TruthAuctionBidsSection.tsx +++ b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionBidsSection.tsx @@ -1,14 +1,14 @@ -import * as commonCopy from '../../../copy/common.js' -import * as forkAuctionCopy from '../../../copy/forkAuction.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as forkAuctionCopy from '@zoltar/ui-zoltar/copy/forkAuction.js' import type { ComponentChildren } from 'preact' import type { Address } from '@zoltar/shared/ethereum' -import { AddressValue } from '../../../components/AddressValue.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { MetricField } from '../../../components/MetricField.js' -import { PaginationControls } from '../../../components/PaginationControls.js' -import { SectionBlock } from '../../../components/SectionBlock.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { PaginationControls } from '@zoltar/ui-core-shared/components/PaginationControls.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' import type { TruthAuctionBidRowViewModel, ViewerTruthAuctionBidRowViewModel } from '../lib/truthAuctionBidViewModels.js' type TruthAuctionBidsSectionProps = { diff --git a/ui/ts/features/truth-auctions/components/TruthAuctionDepthChart.tsx b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionDepthChart.tsx similarity index 95% rename from ui/ts/features/truth-auctions/components/TruthAuctionDepthChart.tsx rename to ui/statoblast/ts/features/truth-auctions/components/TruthAuctionDepthChart.tsx index e4cc655ab..f0ab04b81 100644 --- a/ui/ts/features/truth-auctions/components/TruthAuctionDepthChart.tsx +++ b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionDepthChart.tsx @@ -1,9 +1,9 @@ -import * as commonCopy from '../../../copy/common.js' -import * as forkAuctionCopy from '../../../copy/forkAuction.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as forkAuctionCopy from '@zoltar/ui-zoltar/copy/forkAuction.js' import { useRef } from 'preact/hooks' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { formatCurrencyInputBalance, formatRoundedCurrencyBalance } from '../../../lib/formatters.js' -import { getVisualRatio } from '../../../lib/visualMetrics.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { formatCurrencyInputBalance, formatRoundedCurrencyBalance } from '@zoltar/ui-core-shared/lib/formatters.js' +import { getVisualRatio } from '@zoltar/ui-core-shared/lib/visualMetrics.js' import type { TruthAuctionDepthPoint } from '../lib/truthAuctionBook.js' type TruthAuctionDepthChartProps = { diff --git a/ui/ts/features/truth-auctions/components/TruthAuctionMarketViewSection.tsx b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionMarketViewSection.tsx similarity index 90% rename from ui/ts/features/truth-auctions/components/TruthAuctionMarketViewSection.tsx rename to ui/statoblast/ts/features/truth-auctions/components/TruthAuctionMarketViewSection.tsx index bba301620..ce4bfdd26 100644 --- a/ui/ts/features/truth-auctions/components/TruthAuctionMarketViewSection.tsx +++ b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionMarketViewSection.tsx @@ -1,13 +1,13 @@ -import * as commonCopy from '../../../copy/common.js' -import * as forkAuctionCopy from '../../../copy/forkAuction.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as forkAuctionCopy from '@zoltar/ui-zoltar/copy/forkAuction.js' import type { ComponentChildren } from 'preact' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { PaginationControls } from '../../../components/PaginationControls.js' -import { SectionBlock } from '../../../components/SectionBlock.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { PaginationControls } from '@zoltar/ui-core-shared/components/PaginationControls.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' import { TruthAuctionDepthChart } from './TruthAuctionDepthChart.js' import { getTruthAuctionDispositionClassName, type TruthAuctionDepthPoint } from '../lib/truthAuctionBook.js' -import { getVisualRatio } from '../../../lib/visualMetrics.js' +import { getVisualRatio } from '@zoltar/ui-core-shared/lib/visualMetrics.js' type TruthAuctionMarketViewSectionProps = { clearingTick: bigint | undefined diff --git a/ui/ts/features/truth-auctions/components/TruthAuctionSummaryCard.tsx b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionSummaryCard.tsx similarity index 89% rename from ui/ts/features/truth-auctions/components/TruthAuctionSummaryCard.tsx rename to ui/statoblast/ts/features/truth-auctions/components/TruthAuctionSummaryCard.tsx index 0e4455ab9..a0b107842 100644 --- a/ui/ts/features/truth-auctions/components/TruthAuctionSummaryCard.tsx +++ b/ui/statoblast/ts/features/truth-auctions/components/TruthAuctionSummaryCard.tsx @@ -1,9 +1,9 @@ -import * as commonCopy from '../../../copy/common.js' -import * as forkAuctionCopy from '../../../copy/forkAuction.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as forkAuctionCopy from '@zoltar/ui-zoltar/copy/forkAuction.js' import type { ComponentChildren } from 'preact' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { MetricField } from '../../../components/MetricField.js' -import { SectionBlock } from '../../../components/SectionBlock.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' import { AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL } from '../lib/forkAuction.js' type TruthAuctionSummaryCardProps = { diff --git a/ui/ts/features/truth-auctions/hooks/useForkAuctionInteractionState.ts b/ui/statoblast/ts/features/truth-auctions/hooks/useForkAuctionInteractionState.ts similarity index 97% rename from ui/ts/features/truth-auctions/hooks/useForkAuctionInteractionState.ts rename to ui/statoblast/ts/features/truth-auctions/hooks/useForkAuctionInteractionState.ts index bd782fa33..435de65e6 100644 --- a/ui/ts/features/truth-auctions/hooks/useForkAuctionInteractionState.ts +++ b/ui/statoblast/ts/features/truth-auctions/hooks/useForkAuctionInteractionState.ts @@ -1,8 +1,8 @@ import { useEffect, useRef, useState } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { sameAddress } from '../../../lib/address.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' import type { ForkAuctionSectionProps } from '../../types.js' -import type { ReportingOutcomeKey } from '../../../types/contracts.js' +import type { ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' type PendingParentEscalationClaimSelection = { depositIndexes: bigint[] diff --git a/ui/ts/features/truth-auctions/hooks/useForkAuctionOperations.ts b/ui/statoblast/ts/features/truth-auctions/hooks/useForkAuctionOperations.ts similarity index 96% rename from ui/ts/features/truth-auctions/hooks/useForkAuctionOperations.ts rename to ui/statoblast/ts/features/truth-auctions/hooks/useForkAuctionOperations.ts index bce87f6f2..353b04fe4 100644 --- a/ui/ts/features/truth-auctions/hooks/useForkAuctionOperations.ts +++ b/ui/statoblast/ts/features/truth-auctions/hooks/useForkAuctionOperations.ts @@ -1,7 +1,7 @@ import { useSignal } from '@preact/signals' import { useRef } from 'preact/hooks' -import { useFormState } from '../../../hooks/useFormState.js' -import { useLoadController } from '../../../hooks/useLoadController.js' +import { useFormState } from '@zoltar/ui-core-shared/hooks/useFormState.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' import type { Address } from '@zoltar/shared/ethereum' import { createChildUniverseFromSecurityPool, @@ -21,21 +21,23 @@ import { submitTruthAuctionBid, withdrawForkedEscalationDeposits, } from '../../../protocol/index.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { getErrorMessage } from '../../../lib/errors.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' import { getTruthAuctionBidGuardMessage, getTruthAuctionBidPriceValidationMessage, getTruthAuctionTickAtPrice } from '../lib/truthAuctionBook.js' -import { getReportingOutcomeKey, parseAddressInput, parseBigIntListInput, parseReportingOutcomeInput, parseReportingOutcomeListInput, resolveOptionalAddressInput } from '../../../lib/inputs.js' -import { normalizeAddress } from '../../../lib/address.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' -import { requireDefined } from '../../../lib/required.js' +import { getReportingOutcomeKey, parseAddressInput, parseBigIntListInput, parseReportingOutcomeInput, parseReportingOutcomeListInput, resolveOptionalAddressInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { normalizeAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import { requireDefined } from '@zoltar/ui-core-shared/lib/required.js' import { createForkAuctionSuccessPresentation, createForkAuctionTransactionIntent, createForkAuctionWarningPresentation } from '../../transactionPresentations.js' -import { buildWriteActionConfig, runWriteAction } from '../../../lib/writeAction.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { getDefaultForkAuctionFormState, parseBigIntInput, parseTruthAuctionAmountInput, parseTruthAuctionPriceInput } from '../../markets/lib/marketForm.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { buildWriteActionConfig, runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { parseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { parseTruthAuctionAmountInput, parseTruthAuctionPriceInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { getDefaultForkAuctionFormState } from '../../markets/lib/marketForm.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { ForkAuctionFormState, WriteOperationsParameters } from '../../../types/app.js' -import type { ForkAuctionActionResult, ForkAuctionDetails, ReportingOutcomeKey, TruthAuctionSettlementMode } from '../../../types/contracts.js' +import type { ForkAuctionActionResult, ForkAuctionDetails, ReportingOutcomeKey, TruthAuctionSettlementMode } from '@zoltar/ui-core-shared/types/contracts.js' import type { SettlementSelectedBid } from '../../types.js' type UseForkAuctionOperationsParameters = WriteOperationsParameters & { diff --git a/ui/ts/features/truth-auctions/hooks/useForkWorkflowSelectionState.ts b/ui/statoblast/ts/features/truth-auctions/hooks/useForkWorkflowSelectionState.ts similarity index 98% rename from ui/ts/features/truth-auctions/hooks/useForkWorkflowSelectionState.ts rename to ui/statoblast/ts/features/truth-auctions/hooks/useForkWorkflowSelectionState.ts index dab633460..5117e8a7f 100644 --- a/ui/ts/features/truth-auctions/hooks/useForkWorkflowSelectionState.ts +++ b/ui/statoblast/ts/features/truth-auctions/hooks/useForkWorkflowSelectionState.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'preact/hooks' -import { normalizeAddress } from '../../../lib/address.js' +import { normalizeAddress } from '@zoltar/ui-core-shared/lib/address.js' import type { ForkWorkflowSelectionStage, SelectedPoolView } from '../../security-pools/lib/securityPoolWorkflow.js' type UseForkWorkflowSelectionStateParameters = { diff --git a/ui/ts/features/truth-auctions/hooks/useSelectedAuctionReadState.ts b/ui/statoblast/ts/features/truth-auctions/hooks/useSelectedAuctionReadState.ts similarity index 98% rename from ui/ts/features/truth-auctions/hooks/useSelectedAuctionReadState.ts rename to ui/statoblast/ts/features/truth-auctions/hooks/useSelectedAuctionReadState.ts index a021304ab..f4e2ca7ca 100644 --- a/ui/ts/features/truth-auctions/hooks/useSelectedAuctionReadState.ts +++ b/ui/statoblast/ts/features/truth-auctions/hooks/useSelectedAuctionReadState.ts @@ -1,12 +1,12 @@ import { useEffect, useRef, useState } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' import { loadAllSecurityPools, loadForkAuctionDetails, loadForkOutcomeMigrationSeedStatus } from '../../../protocol/index.js' -import { sameAddress } from '../../../lib/address.js' -import { createConnectedReadClient } from '../../../lib/clients.js' -import { getErrorMessage } from '../../../lib/errors.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { createConnectedReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' import { getCurrentSelectedPoolForkAuctionDetails, shouldReloadSelectedPoolDetails, type ForkWorkflowSelectionStage } from '../../security-pools/lib/securityPoolWorkflow.js' import type { ForkAuctionSectionProps } from '../../types.js' -import type { ListedSecurityPool, ReadClient, ReportingOutcomeKey } from '../../../types/contracts.js' +import type { ListedSecurityPool, ReadClient, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' type ForkOutcomeMigrationSeedStatus = Awaited> diff --git a/ui/ts/features/truth-auctions/hooks/useTruthAuctionBookData.ts b/ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionBookData.ts similarity index 98% rename from ui/ts/features/truth-auctions/hooks/useTruthAuctionBookData.ts rename to ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionBookData.ts index 19407c3aa..8814568af 100644 --- a/ui/ts/features/truth-auctions/hooks/useTruthAuctionBookData.ts +++ b/ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionBookData.ts @@ -1,11 +1,11 @@ import { useEffect, useState } from 'preact/hooks' import { type Address, zeroAddress } from '@zoltar/shared/ethereum' import { loadTruthAuctionActiveTickPage, loadTruthAuctionBidderBidPage, loadTruthAuctionTickBidPage } from '../../../protocol/index.js' -import { createConnectedReadClient } from '../../../lib/clients.js' -import { getErrorMessage } from '../../../lib/errors.js' +import { createConnectedReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' import { sortTruthAuctionBidsByPriority, sortTruthAuctionTickSummariesDescending } from '../lib/truthAuctionBook.js' import type { ForkWorkflowSelectionStage } from '../../security-pools/lib/securityPoolWorkflow.js' -import type { ReadClient, TruthAuctionBidView, TruthAuctionTickSummary } from '../../../types/contracts.js' +import type { ReadClient, TruthAuctionBidView, TruthAuctionTickSummary } from '@zoltar/ui-core-shared/types/contracts.js' import { useTruthAuctionPaginationState } from './useTruthAuctionPaginationState.js' const TRUTH_AUCTION_TICK_PAGE_SIZE = 25 diff --git a/ui/ts/features/truth-auctions/hooks/useTruthAuctionPaginationState.ts b/ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionPaginationState.ts similarity index 100% rename from ui/ts/features/truth-auctions/hooks/useTruthAuctionPaginationState.ts rename to ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionPaginationState.ts diff --git a/ui/ts/features/truth-auctions/hooks/useTruthAuctionSettlementActionState.ts b/ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionSettlementActionState.ts similarity index 97% rename from ui/ts/features/truth-auctions/hooks/useTruthAuctionSettlementActionState.ts rename to ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionSettlementActionState.ts index a25a5e825..6ed768326 100644 --- a/ui/ts/features/truth-auctions/hooks/useTruthAuctionSettlementActionState.ts +++ b/ui/statoblast/ts/features/truth-auctions/hooks/useTruthAuctionSettlementActionState.ts @@ -1,10 +1,10 @@ import { useEffect, useState } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { sameAddress } from '../../../lib/address.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' import { createTruthAuctionSettlementActionState, getTruthAuctionSettlementAction, reduceTruthAuctionSettlementActionState } from '../lib/truthAuctionSettlementActionState.js' import { getTruthAuctionSettlementBidKey, getTruthAuctionSettlementSelectionState, type TruthAuctionSettlementBidRow } from '../lib/truthAuctionSettlement.js' import type { ForkWorkflowSelectionStage } from '../../security-pools/lib/securityPoolWorkflow.js' -import type { ForkAuctionActionResult } from '../../../types/contracts.js' +import type { ForkAuctionActionResult } from '@zoltar/ui-core-shared/types/contracts.js' import type { SettlementSelectedBid } from '../../types.js' type SettlementBidKeyUpdater = string[] | ((currentKeys: string[]) => string[]) diff --git a/ui/ts/features/truth-auctions/lib/forkAuction.ts b/ui/statoblast/ts/features/truth-auctions/lib/forkAuction.ts similarity index 87% rename from ui/ts/features/truth-auctions/lib/forkAuction.ts rename to ui/statoblast/ts/features/truth-auctions/lib/forkAuction.ts index 3ff5cb720..3625f5efa 100644 --- a/ui/ts/features/truth-auctions/lib/forkAuction.ts +++ b/ui/statoblast/ts/features/truth-auctions/lib/forkAuction.ts @@ -1,8 +1,8 @@ -import type { ForkOutcomeKey, ReportingOutcomeKey, SecurityPoolSystemState, TruthAuctionMetrics } from '../../../types/contracts.js' -import { assertNever } from '../../../lib/assert.js' -import { getTimeRemaining as getSharedTimeRemaining } from '../../../lib/time.js' -import { getReportingOutcomeLabel } from '../../reporting/lib/reporting.js' -import { deriveHasForkActivity } from '../../../protocol/forkActivity.js' +import type { ForkOutcomeKey, ReportingOutcomeKey, SecurityPoolSystemState, TruthAuctionMetrics } from '@zoltar/ui-core-shared/types/contracts.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { getTimeRemaining as getSharedTimeRemaining } from '@zoltar/ui-core-shared/lib/time.js' +import { getReportingOutcomeLabel } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' +import { deriveHasForkActivity } from '@zoltar/ui-zoltar/protocol/forkActivity.js' export { deriveHasForkActivity } diff --git a/ui/ts/features/truth-auctions/lib/truthAuctionBidViewModels.ts b/ui/statoblast/ts/features/truth-auctions/lib/truthAuctionBidViewModels.ts similarity index 95% rename from ui/ts/features/truth-auctions/lib/truthAuctionBidViewModels.ts rename to ui/statoblast/ts/features/truth-auctions/lib/truthAuctionBidViewModels.ts index f95fc58e2..05df97d32 100644 --- a/ui/ts/features/truth-auctions/lib/truthAuctionBidViewModels.ts +++ b/ui/statoblast/ts/features/truth-auctions/lib/truthAuctionBidViewModels.ts @@ -1,10 +1,10 @@ import type { Address } from '@zoltar/shared/ethereum' import type { ForkWorkflowSelectionStage } from '../../security-pools/lib/securityPoolWorkflow.js' import { getTruthAuctionSettlementBidKey } from './truthAuctionSettlement.js' -import { sameAddress } from '../../../lib/address.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' import { getTruthAuctionBidDisposition, getTruthAuctionDispositionClassName, getTruthAuctionPriceAtTick } from './truthAuctionBook.js' -import { formatCurrencyInputBalance, formatValueWithUnit } from '../../../lib/formatters.js' -import type { TruthAuctionBidView, TruthAuctionMetrics } from '../../../types/contracts.js' +import { formatCurrencyInputBalance, formatValueWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import type { TruthAuctionBidView, TruthAuctionMetrics } from '@zoltar/ui-core-shared/types/contracts.js' type LocalSettlementBidStatus = 'claimed' | 'refunded' diff --git a/ui/ts/features/truth-auctions/lib/truthAuctionBook.ts b/ui/statoblast/ts/features/truth-auctions/lib/truthAuctionBook.ts similarity index 98% rename from ui/ts/features/truth-auctions/lib/truthAuctionBook.ts rename to ui/statoblast/ts/features/truth-auctions/lib/truthAuctionBook.ts index bea0cbcb0..aee9ae9fa 100644 --- a/ui/ts/features/truth-auctions/lib/truthAuctionBook.ts +++ b/ui/statoblast/ts/features/truth-auctions/lib/truthAuctionBook.ts @@ -1,9 +1,9 @@ import { TRUTH_AUCTION_MAX_TICK, TRUTH_AUCTION_MIN_TICK, TRUTH_AUCTION_PRICE_PRECISION } from '@zoltar/shared/truthAuctionTickMath' -import type { TruthAuctionBidView, TruthAuctionMetrics, TruthAuctionTickSummary } from '../../../types/contracts.js' -import { getWalletActiveAppChainGuardState } from '../../../lib/actionGuards.js' -import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' -import { getTruthAuctionPriceAtTick, getTruthAuctionTickAtPrice, TRUTH_AUCTION_MIN_SUPPORTED_TICK } from '../../../protocol/truthAuctionMath.js' -import { tryParseTruthAuctionAmountInput, tryParseTruthAuctionPriceInput } from '../../markets/lib/marketForm.js' +import type { TruthAuctionBidView, TruthAuctionMetrics, TruthAuctionTickSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWalletActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' +import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { getTruthAuctionPriceAtTick, getTruthAuctionTickAtPrice, TRUTH_AUCTION_MIN_SUPPORTED_TICK } from '@zoltar/ui-core-shared/protocol/truthAuctionMath.js' +import { tryParseTruthAuctionAmountInput, tryParseTruthAuctionPriceInput } from '@zoltar/ui-core-shared/lib/formInputs.js' export { TRUTH_AUCTION_MAX_TICK, TRUTH_AUCTION_MIN_TICK, TRUTH_AUCTION_PRICE_PRECISION } export { getTruthAuctionPriceAtTick, getTruthAuctionTickAtPrice, TRUTH_AUCTION_MIN_SUPPORTED_TICK } diff --git a/ui/ts/features/truth-auctions/lib/truthAuctionSettlement.ts b/ui/statoblast/ts/features/truth-auctions/lib/truthAuctionSettlement.ts similarity index 97% rename from ui/ts/features/truth-auctions/lib/truthAuctionSettlement.ts rename to ui/statoblast/ts/features/truth-auctions/lib/truthAuctionSettlement.ts index 27e69a606..730a6e935 100644 --- a/ui/ts/features/truth-auctions/lib/truthAuctionSettlement.ts +++ b/ui/statoblast/ts/features/truth-auctions/lib/truthAuctionSettlement.ts @@ -1,6 +1,6 @@ import type { Address } from '@zoltar/shared/ethereum' -import type { TruthAuctionBidView, TruthAuctionMetrics } from '../../../types/contracts.js' -import { sameAddress } from '../../../lib/address.js' +import type { TruthAuctionBidView, TruthAuctionMetrics } from '@zoltar/ui-core-shared/types/contracts.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' import { getTruthAuctionBidDisposition, getTruthAuctionBidSettlementEstimate, getTruthAuctionWinningThresholdPrice, type TruthAuctionBidDisposition } from './truthAuctionBook.js' export type TruthAuctionSettlementBidRow = { diff --git a/ui/ts/features/truth-auctions/lib/truthAuctionSettlementActionState.ts b/ui/statoblast/ts/features/truth-auctions/lib/truthAuctionSettlementActionState.ts similarity index 100% rename from ui/ts/features/truth-auctions/lib/truthAuctionSettlementActionState.ts rename to ui/statoblast/ts/features/truth-auctions/lib/truthAuctionSettlementActionState.ts diff --git a/ui/ts/features/types.ts b/ui/statoblast/ts/features/types.ts similarity index 56% rename from ui/ts/features/types.ts rename to ui/statoblast/ts/features/types.ts index 788f3cb65..564b16aa6 100644 --- a/ui/ts/features/types.ts +++ b/ui/statoblast/ts/features/types.ts @@ -1,24 +1,16 @@ -import type { ComponentChildren } from 'preact' import type { Address } from '@zoltar/shared/ethereum' -import type { AccountState, ForkAuctionFormState, MarketFormState, OpenOracleCreateFormState, OpenOracleFormState, ReportingFormState, SecurityPoolFormState, SecurityVaultFormState, TradingFormState, ZoltarMigrationFormState } from '../types/app.js' +import type { AccountState, ForkAuctionFormState, SecurityPoolFormState, SecurityVaultFormState, TradingFormState } from '../types/app.js' +import type { ReportingFormState } from '@zoltar/ui-zoltar/types/app.js' import type { - DeploymentStatus, - DeploymentStepId, ForkAuctionActionResult, ForkAuctionDetails, LiquidationApprovalDetails, LiquidationFundingPreview, ListedSecurityPool, - MarketCreationResult, MarketDetails, - MarketDetailsPage, OpenOracleActionResult, - OpenOracleReportDetails, - OpenOracleReportSummaryPage, - OpenOracleWithdrawableBalances, OracleManagerDetails, ReadClient, - ReportingActionResult, ReportingDetails, ReportingOutcomeKey, SecurityPoolBrowsePage, @@ -30,36 +22,16 @@ import type { TradingActionResult, TradingDetails, ZoltarUniverseSummary, -} from '../types/contracts.js' -import type { ActionAvailability, ReadinessAction } from '../types/components.js' +} from '@zoltar/ui-core-shared/types/contracts.js' +import type { ReadinessAction } from '@zoltar/ui-core-shared/types/components.js' import type { SecurityPoolLifecycleState } from './security-pools/lib/securityPoolState.js' import type { ForkAuctionStageView } from './truth-auctions/lib/forkAuction.js' -import type { OpenOracleCreateContractFieldErrors, OpenOracleDisputeSubmissionDetails } from './open-oracle/lib/openOracle.js' -import type { LoadableValueState } from '../lib/loadState.js' import type { SecurityPoolStateModel } from './security-pools/lib/securityPoolState.js' import type { ForkWorkflowSelectionStage } from './security-pools/lib/securityPoolWorkflow.js' -import type { TokenApprovalState } from '../lib/tokenApproval.js' -import type { UserMessagePresentation } from '../lib/userCopy.js' -import type { ReadBackendStatus } from '../lib/chainBackend.js' +import type { TokenApprovalState } from '@zoltar/ui-core-shared/lib/tokenApproval.js' +import type { ReportingRouteContentProps } from '@zoltar/ui-zoltar/features/types.js' -export type * from '../types/components.js' - -type RepPerEthPriceProps = { - repPerEthPrice: bigint | undefined - repPerEthSource: 'v4' | 'v3' | 'mock' | undefined - repPerEthSourceUrl: string | undefined -} - -export type RepPriceFailure = 'no-liquidity' | 'rpc-error' - -export type OutcomeChipRowProps = { - className?: string - items: Array<{ - key: string - label: ComponentChildren - tone?: 'default' | 'danger' | 'muted' | 'success' | 'warning' - }> -} +export type * from '@zoltar/ui-core-shared/types/components.js' export type CollateralizationCircleProps = { collateralizationPercent: bigint | undefined @@ -72,40 +44,6 @@ export type CollateralizationCircleProps = { warningThreshold?: number } -export type ScalarOutcomePickerProps = { - action?: ComponentChildren - details: { - answerUnit?: string - displayValueMax?: bigint - displayValueMin?: bigint - maxValueLabel?: ComponentChildren - minValueLabel?: ComponentChildren - numTicks: bigint - } - disabled?: boolean - isInvalid: boolean - label: ComponentChildren - onInvalidChange: (invalid: boolean) => void - onSelectedTickChange: (tick: string) => void - selectedOutcomeLabel: ComponentChildren - selectedTick: string - selectedTickLabel: ComponentChildren - showMinMax?: boolean -} - -export type OutcomeSelectionListProps = { - className?: string - emptyMessage?: ComponentChildren - items: Array<{ - details?: ComponentChildren - disabled?: boolean - key: string - label: ComponentChildren - onSelect: () => void - selected: boolean - }> -} - export type VaultMetricGridProps = { associatedRepPerCapacityBps?: bigint | undefined badDebtAttoEth?: bigint | undefined @@ -121,127 +59,15 @@ export type VaultMetricGridProps = { claimableFeesAttoEth: bigint | undefined } & RepPerEthPriceProps -export type DeploymentSectionProps = { - title: string - completedGroup?: boolean - steps: DeploymentStatus[] - allSteps: DeploymentStatus[] - accountAddress: Address | undefined - isOnActiveAppChain: boolean - busyStepId: DeploymentStepId | undefined - deploymentStateReady: boolean - deploymentStatusReasonElementId?: string | undefined - onDeploy: (stepId: DeploymentStepId) => Promise +type RepPerEthPriceProps = { + repPerEthPrice: bigint | undefined + repPerEthSource: 'v4' | 'v3' | 'mock' | undefined + repPerEthSourceUrl: string | undefined } -export type OverviewPanelsProps = { - accountState: AccountState - isConnectingWallet: boolean - isManagingWallet: boolean - walletBootstrapComplete: boolean - universeRepBalanceAttoRep: bigint | undefined - isLoadingUniverseRepBalance: boolean - universeForkTime?: bigint | undefined - universeHasForked?: boolean | undefined - universePresentation: UserMessagePresentation | undefined - universeLabel: string - isRefreshing: boolean - repUsdcPrice: bigint | undefined - repUsdcFailure: RepPriceFailure | undefined - repUsdcSource: 'v4' | 'v3' | 'mock' | undefined - repUsdcSourceUrl: string | undefined - isLoadingRepPrices: boolean - isRefreshingRepPrices: boolean - onConnect: () => void - onChangeWallet: () => void - onDisconnectWallet: () => void - onGoToGenesisUniverse: () => void - onRefreshRepPrices: () => void - onSwitchNetwork: () => void - readBackendStatus?: ReadBackendStatus - repPerEthFailure: RepPriceFailure | undefined -} & RepPerEthPriceProps - -export type ZoltarView = 'create' | 'fork' | 'migrate' | 'questions' - export type SecurityPoolsView = 'browse' | 'create' | 'operate' -export type DeploymentRouteContentProps = { - accountAddress: Address | undefined - busyStepId: DeploymentStepId | undefined - deploymentStateReady: boolean - deploymentStatusError: string | undefined - deploymentSections: { title: string; steps: DeploymentStatus[] }[] - deploymentStatuses: DeploymentStatus[] - isLoadingDeploymentStatuses: boolean - isOnActiveAppChain: boolean - deployNextMissingPending: boolean - onDeploy: (stepId: DeploymentStepId) => Promise - onDeployNextMissing: () => void - onRetryDeploymentStatus: () => void -} - -export type MarketRouteContentProps = { - accountState: AccountState - activeUniverseId: bigint - activeView: ZoltarView - securityPools?: ListedSecurityPool[] - hasLoadedSecurityPools: boolean - loadingSecurityPools: boolean - onLoadSecurityPools: () => void - securityPoolsLoadError: string | undefined - environmentRefreshKey: number - onApproveZoltarForkRep: (amount?: bigint) => void - onCreateChildUniverseForOutcomeIndex: (outcomeIndex: bigint) => void - onCreateMarket: () => void - onForkZoltar: () => void - onMigrateInternalRep: () => void - onPrepareRepForMigration: () => void - marketCreating: boolean - marketError: string | undefined - marketForm: MarketFormState - marketResult: MarketCreationResult | undefined - onActiveViewChange: (view: ZoltarView) => void - onResetMarket: () => void - loadingZoltarQuestionCount: boolean - loadingZoltarQuestion: boolean - loadingZoltarQuestions: boolean - hasLoadedZoltarQuestions: boolean - zoltarForkActiveAction: 'approve' | 'fork' | undefined - loadingZoltarUniverse: boolean - zoltarUniverseState: LoadableValueState - onLoadZoltarQuestions: () => Promise - onLoadZoltarQuestion: (questionId: string) => Promise - onLoadZoltarQuestionPage: (pageIndex: number, pageSize: number) => Promise - onMarketFormChange: (update: Partial) => void - onUseQuestionForFork: (questionId: string) => void - onUseQuestionForPool: (questionId: string) => void - onZoltarMigrationFormChange: (update: Partial) => void - zoltarQuestionCount: bigint | undefined - zoltarQuestionLookupError: string | undefined - zoltarQuestionLookupId: string | undefined - zoltarQuestionPage: MarketDetailsPage | undefined - zoltarForkApproval: TokenApprovalState - zoltarForkError: string | undefined - loadingZoltarForkAccess: boolean - zoltarChildUniverseError: string | undefined - zoltarChildUniversePendingOutcomeIndex: bigint | undefined - zoltarForkPending: boolean - zoltarForkQuestionId: string - zoltarForkRepBalanceAttoRep: bigint | undefined - zoltarMigrationError: string | undefined - zoltarMigrationForm: ZoltarMigrationFormState - zoltarMigrationChildRepBalancesAttoRep: Record - zoltarMigrationPending: boolean - zoltarMigrationPreparedRepBalanceAttoRep: bigint | undefined - zoltarQuestions: MarketDetails[] - zoltarQuestionsError: string | undefined - zoltarMigrationActiveAction: 'prepare' | 'split' | undefined - zoltarUniverse: ZoltarUniverseSummary | undefined - onZoltarForkQuestionIdChange: (questionId: string) => void -} - -export type SecurityPoolRouteContentProps = { +type SecurityPoolRouteContentProps = { accountState: AccountState availableQuestionsContextKey: string availableQuestions: MarketDetails[] @@ -265,7 +91,6 @@ export type SecurityPoolRouteContentProps = { securityPoolResult: SecurityPoolCreationResult | undefined } & RepPerEthPriceProps -export type MarketSectionProps = MarketRouteContentProps export type SecurityPoolSectionProps = SecurityPoolRouteContentProps & { activeUniverseId: bigint onReturnToBrowse?: () => void @@ -309,7 +134,7 @@ type LiquidationModalStateProps = { poolOracleManagerDetails: OracleManagerDetails | undefined } -export type SecurityPoolsOverviewRouteContentProps = { +type SecurityPoolsOverviewRouteContentProps = { accountState: AccountState activeUniverseId: bigint environmentRefreshKey: number @@ -337,7 +162,7 @@ export type SecurityPoolWorkflowRouteContentProps = LiquidationModalStateProps & onCreatePool: () => void onOpenLiquidationModal: (managerAddress: Address, securityPoolAddress: Address, vaultAddress: Address, maxAmount: bigint | undefined) => void onReturnToCurrentUniverse?: () => void - onSwitchToPoolUniverse?: (universeId: bigint, securityPoolAddress: Address) => void + onSwitchToPoolUniverse?: (universeId: bigint, securityPoolAddress: string) => void onExecutePendingPoolOperation: (managerAddress: Address, operationId: bigint, securityPoolAddress: Address, universeId: bigint) => void onRefreshSelectedPoolData: (securityPoolAddress?: string) => void onRequestPoolPrice: (managerAddress: Address, securityPoolAddress: Address, reviewedRequestValueAttoEth: bigint, universeId: bigint) => void @@ -371,7 +196,7 @@ export type SecurityPoolsSectionProps = { workflow: SecurityPoolWorkflowRouteContentProps } -export type SecurityVaultRouteContentProps = { +type SecurityVaultRouteContentProps = { accountState: AccountState loadingSecurityVault: boolean onApproveRep: (amount?: bigint) => void @@ -415,92 +240,7 @@ export type SecurityVaultSectionProps = SecurityVaultRouteContentProps & { showHeader?: boolean } -type OpenOracleRouteContentProps = { - accountState: AccountState - onApproveToken1: (amount?: bigint) => void - onApproveToken2: (amount?: bigint) => void - onCancelOpenOracleWithdrawalBalanceCheck: () => void - onCreateOpenOracleGame: () => void - onDisputeReport: () => void - onLoadOracleReport: (reportId?: string) => void - onOpenOracleFormChange: (update: Partial) => void - onOpenOracleCreateFormChange: (update: Partial) => void - onSettleReport: () => void - onWithdrawOpenOracleBalance: (balance: keyof OpenOracleWithdrawableBalances, reviewedAmount: bigint) => void - loadingOpenOracleCreate: boolean - openOracleActiveAction: OpenOracleActionResult['action'] | undefined - openOracleActiveWithdrawalBalance: keyof OpenOracleWithdrawableBalances | undefined - openOracleError: string | undefined - openOracleTokenAccessState: { - token1Approval: TokenApprovalState - token1Balance: bigint | undefined - token1BalanceError: string | undefined - token1Decimals: number | undefined - token2Approval: TokenApprovalState - token2Balance: bigint | undefined - token2BalanceError: string | undefined - token2Decimals: number | undefined - tokenAccessLoadingInitial: boolean - tokenAccessRefreshing: boolean - } - openOracleDisputeSubmission: OpenOracleDisputeSubmissionDetails | undefined - openOracleCreateForm: OpenOracleCreateFormState - openOracleCreateFieldErrors?: OpenOracleCreateContractFieldErrors - openOracleForm: OpenOracleFormState - openOracleReportLookupState: OpenOracleReportLookupState - openOracleReportDetails: OpenOracleReportDetails | undefined - openOracleResult: OpenOracleActionResult | undefined - openOracleWithdrawalBalanceChecking: boolean - openOracleWithdrawalReviewMessage: { balance: keyof OpenOracleWithdrawableBalances; message: string } | undefined - openOracleWithdrawableBalances: OpenOracleWithdrawableBalances | undefined - openOracleWithdrawableBalancesError: string | undefined - openOracleWithdrawableBalancesLoading: boolean -} - -export type OpenOracleReportLookupState = 'unknown' | 'loading' | 'ready' | 'missing' | 'load-failed' - -export type OpenOracleView = 'browse' | 'create' | 'selected-report' - -export type OpenOracleSectionProps = OpenOracleRouteContentProps & { - activeView: OpenOracleView - environmentReady: boolean - environmentRefreshKey: number - loadBrowseReports?: (pageIndex: number, pageSize: number) => Promise - onActiveViewChange: (view: OpenOracleView) => void -} - -export type ReportingRouteContentProps = { - accountState: AccountState - loadingReportingDetails: boolean - onLoadReporting: () => void - onReportOutcome: () => void - onReportingFormChange: (update: Partial) => void - onWithdrawEscalation: (outcome: ReportingOutcomeKey, depositIndexes?: bigint[]) => void - reportingActiveAction: ReportingActionResult['action'] | undefined - reportingDetails: ReportingDetails | undefined - reportingError: string | undefined - reportingForm: ReportingFormState - reportingResult: ReportingActionResult | undefined -} - -export type ReportingSectionProps = ReportingRouteContentProps & { - currentTimestamp?: bigint | undefined - embedInCard?: boolean - forkAlreadyTriggered?: boolean | undefined - lockedReason?: string | undefined - mode?: 'full-reporting' | 'withdraw-only' - onOpenForkWorkflow?: (() => void) | undefined - onOpenPriceOracle?: (() => void) | undefined - onTriggerZoltarFork?: (() => void) | undefined - previewMarketDetails?: MarketDetails | undefined - reportActionGuardMessage?: string | undefined - showHeader?: boolean - showSecurityPoolAddressInput?: boolean - triggerZoltarForkAvailability?: ActionAvailability | undefined - triggerZoltarForkPending?: boolean | undefined -} - -export type TradingRouteContentProps = { +type TradingRouteContentProps = { accountState: AccountState loadingTradingForkUniverse: boolean loadingTradingDetails: boolean @@ -533,7 +273,7 @@ export type SettlementSelectedBid = { bidIndex: bigint } -export type ForkAuctionRouteContentProps = { +type ForkAuctionRouteContentProps = { accountState: AccountState forkAuctionDetails: ForkAuctionDetails | undefined forkAuctionActiveAction: ForkAuctionActionResult['action'] | undefined diff --git a/ui/statoblast/ts/index.dev.ts b/ui/statoblast/ts/index.dev.ts new file mode 100644 index 000000000..b9a7e28be --- /dev/null +++ b/ui/statoblast/ts/index.dev.ts @@ -0,0 +1,2 @@ +import './liveReload.js' +import './index.js' diff --git a/ui/statoblast/ts/index.ts b/ui/statoblast/ts/index.ts new file mode 100644 index 000000000..8705a9c12 --- /dev/null +++ b/ui/statoblast/ts/index.ts @@ -0,0 +1,19 @@ +import { createElement } from 'preact' +import { installRouting } from '@zoltar/ui-core-shared/lib/routing.js' +import { mountApp } from '@zoltar/ui-core-shared/app/appRoot.js' +import { registerStatoblastSimulationScenarios } from './simulation/index.js' +import { App } from './app/App.js' +import { initializeStatoblastActiveEnvironment } from './app/activeEnvironment.js' + +installRouting({ + defaultRoute: 'security-pools', + routes: [ + { hash: '#/deploy', name: 'deploy' }, + { hash: '#/security-pools', name: 'security-pools', queryParameters: new Set(['questionId', 'securityPool', 'securityPoolsView', 'selectedPoolView']) }, + { hash: '#/open-oracle', name: 'open-oracle', queryParameters: new Set(['openOracleReportId', 'openOracleView']) }, + ], +}) + +registerStatoblastSimulationScenarios() + +void mountApp({ initialize: initializeStatoblastActiveEnvironment, root: () => createElement(App, {}) }) diff --git a/ui/ts/liveReload.ts b/ui/statoblast/ts/liveReload.ts similarity index 100% rename from ui/ts/liveReload.ts rename to ui/statoblast/ts/liveReload.ts diff --git a/ui/statoblast/ts/protocol/deployment.ts b/ui/statoblast/ts/protocol/deployment.ts new file mode 100644 index 000000000..14dcd4904 --- /dev/null +++ b/ui/statoblast/ts/protocol/deployment.ts @@ -0,0 +1,186 @@ +import type { NetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { getRuntimeNetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { encodeDeployData, keccak256, type Address, type Hash, type Hex } from '@zoltar/shared/ethereum' +import type { DeploymentStatus, DeploymentStatusSnapshot, DeploymentStep, ReadClient, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { getDeploymentSteps as getZoltarDeploymentSteps, withExpectedDeploymentRuntimeCodeHashes } from '@zoltar/ui-zoltar/protocol/deployment.js' +import { getInfraContractAddresses, getEscalationGameFactoryByteCode, getSecurityPoolFactoryByteCode, getSecurityPoolForkerByteCode } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' +import { DeploymentStatusOracle_DeploymentStatusOracle } from '@zoltar/ui-core-shared/contractArtifact.js' +import { statoblast_EscalationGameClaimDelegate_EscalationGameClaimDelegate } from '@zoltar/ui-core-shared/contractArtifact.js' +import { createDeploymentStatusOracleAddressHelper } from '@zoltar/shared/deploymentAddresses' +import { PROXY_DEPLOYER_ADDRESS, ZERO_SALT } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' + +export { loadErc20Allowance, loadErc20Balance } from '@zoltar/ui-zoltar/protocol/deployment.js' + +export function getDeploymentSteps(profile: NetworkProfile = getRuntimeNetworkProfile(), wait?: Parameters[1]): DeploymentStep[] { + const addresses = getInfraContractAddresses(profile) + const steps: DeploymentStep[] = [ + // Statoblast replaces the deployment status oracle step: the statoblast oracle + // must monitor the four additional statoblast contracts, so it is deployed with + // a different constructor argument list (and therefore a different address). + ...getZoltarDeploymentSteps(profile, wait).map(step => + step.id === 'deploymentStatusOracle' + ? { + ...step, + address: getDeploymentStatusOracleAddress(profile), + deploy: async (client: WriteClient) => await deployViaProxy(client, getDeploymentStatusOracleByteCode(profile)), + } + : step, + ), + { + id: 'securityPoolForker', + label: 'Security Pool Forker', + address: addresses.securityPoolForker, + dependencies: ['proxyDeployer', 'scalarOutcomes', 'securityPoolUtils', 'zoltar'], + deploy: async client => await deployViaProxy(client, getSecurityPoolForkerByteCode(addresses.zoltar)), + }, + { + id: 'escalationGameClaimDelegate', + label: 'Escalation Claim Checkpoint Delegate', + address: addresses.escalationGameClaimDelegate, + dependencies: ['proxyDeployer'], + deploy: async client => await deployViaProxy(client, `0x${statoblast_EscalationGameClaimDelegate_EscalationGameClaimDelegate.evm.bytecode.object}`), + }, + { + id: 'escalationGameFactory', + label: 'Escalation Game Factory', + address: addresses.escalationGameFactory, + dependencies: ['proxyDeployer', 'escalationGameClaimDelegate'], + deploy: async client => await deployViaProxy(client, getEscalationGameFactoryByteCode(addresses.escalationGameClaimDelegate)), + }, + { + id: 'securityPoolFactory', + label: 'Security Pool Factory', + address: addresses.securityPoolFactory, + dependencies: ['proxyDeployer', 'securityPoolForker', 'zoltarQuestionData', 'escalationGameFactory', 'openOracle', 'zoltar', 'shareTokenFactory', 'uniformPriceDualCapBatchAuctionFactory', 'priceOracleManagerAndOperatorQueuerFactory', 'securityPoolUtils'], + deploy: async client => + await deployViaProxy( + client, + getSecurityPoolFactoryByteCode({ + escalationGameFactory: addresses.escalationGameFactory, + openOracle: addresses.openOracle, + priceOracleManagerAndOperatorQueuerFactory: addresses.priceOracleManagerAndOperatorQueuerFactory, + securityPoolForker: addresses.securityPoolForker, + shareTokenFactory: addresses.shareTokenFactory, + uniformPriceDualCapBatchAuctionFactory: addresses.uniformPriceDualCapBatchAuctionFactory, + zoltar: addresses.zoltar, + zoltarQuestionData: addresses.zoltarQuestionData, + }), + ), + }, + ] + return withExpectedDeploymentRuntimeCodeHashes(steps, profile) +} + +async function deployViaProxy(client: WriteClient, bytecode: Hex): Promise { + const hash = await client.sendTransaction({ + to: PROXY_DEPLOYER_ADDRESS, + data: bytecode, + }) + const { hash: resolvedHash } = await waitForSubmittedTransactionReceipt(client, hash) + return resolvedHash +} + +async function waitForSubmittedTransactionReceipt(client: WriteClient, hash: Hash) { + const { waitForSubmittedTransactionReceipt: waitReceipt } = await import('@zoltar/ui-zoltar/protocol/core.js') + return await waitReceipt(client, hash) +} + +function getDeploymentStatusOracleStepAddresses(profile = getRuntimeNetworkProfile()): Address[] { + const addresses = getInfraContractAddresses(profile) + return [ + PROXY_DEPLOYER_ADDRESS, + ...(profile.id === 'sepolia' ? [profile.wethAddress, profile.genesisRepTokenAddress] : []), + addresses.multicall3, + addresses.uniformPriceDualCapBatchAuctionFactory, + addresses.scalarOutcomes, + addresses.securityPoolUtils, + addresses.openOracle, + addresses.zoltarQuestionData, + addresses.zoltar, + addresses.shareTokenFactory, + addresses.priceOracleManagerAndOperatorQueuerFactory, + addresses.securityPoolForker, + addresses.escalationGameClaimDelegate, + addresses.escalationGameFactory, + addresses.securityPoolFactory, + ] satisfies Address[] +} + +function getDeploymentStatusOracleByteCode(profile = getRuntimeNetworkProfile()): Hex { + return encodeDeployData({ + abi: DeploymentStatusOracle_DeploymentStatusOracle.abi, + bytecode: `0x${DeploymentStatusOracle_DeploymentStatusOracle.evm.bytecode.object}`, + args: [getDeploymentStatusOracleStepAddresses(profile)], + }) +} + +function getDeploymentStatusOracleAddress(profile = getRuntimeNetworkProfile()): Address { + return createDeploymentStatusOracleAddressHelper({ + deploymentStatusOracleBytecode: () => getDeploymentStatusOracleByteCode(profile), + proxyDeployerAddress: PROXY_DEPLOYER_ADDRESS, + zeroSalt: ZERO_SALT, + }).getDeploymentStatusOracleAddress() +} + +async function loadDeploymentStatusOracleMask(client: Pick): Promise { + return BigInt( + await client.readContract({ + abi: DeploymentStatusOracle_DeploymentStatusOracle.abi, + functionName: 'getDeploymentMask', + address: getDeploymentStatusOracleAddress(), + args: [], + }), + ) +} + +function getDeploymentStatusSnapshot(steps: readonly DeploymentStep[], deployedMask: bigint, deploymentStatusOracleDeployed: boolean): DeploymentStatusSnapshot { + let maskIndex = 0n + const deploymentStatuses: DeploymentStatus[] = steps.map(step => { + if (step.id === 'deploymentStatusOracle') return { ...step, deployed: deploymentStatusOracleDeployed } + const deployed = (deployedMask & (1n << maskIndex)) !== 0n + maskIndex += 1n + return { ...step, deployed } + }) + return { + applicationDeploymentComplete: deploymentStatuses.every(step => step.deployed), + deploymentStatuses, + } +} + +function assertStepRuntimeCode(step: DeploymentStep, code: Hex | undefined): boolean { + if (step.trustedSimulationCodePresence) return true + if (code === undefined || code === '0x') return false + if (step.expectedRuntimeCodeHash === undefined) throw new Error(`Exact runtime-code verification is unavailable for deployment step ${step.id} on the active network`) + if (keccak256(code) !== step.expectedRuntimeCodeHash) throw new Error(`Unexpected runtime code for ${step.id} at ${step.address}`) + return true +} + +export async function loadDeploymentStatusOracleSnapshot(client: Pick): Promise { + const profile = getRuntimeNetworkProfile() + const steps = getDeploymentSteps(profile) + const oracleAddress = getDeploymentStatusOracleAddress(profile) + const oracleCode = await client.getCode({ address: oracleAddress }) + if (profile.id === 'simulation') { + if (oracleCode === undefined || oracleCode === '0x') { + const proxyDeployerCode = await client.getCode({ address: PROXY_DEPLOYER_ADDRESS }) + return getDeploymentStatusSnapshot(steps, proxyDeployerCode === undefined || proxyDeployerCode === '0x' ? 0n : 1n, false) + } + return getDeploymentStatusSnapshot(steps, await loadDeploymentStatusOracleMask(client), true) + } + const oracleStep = steps.find(step => step.id === 'deploymentStatusOracle') + const proxyStep = steps.find(step => step.id === 'proxyDeployer') + if (oracleStep === undefined || proxyStep === undefined) throw new Error('Deployment plan is missing required verification steps') + if (!assertStepRuntimeCode(oracleStep, oracleCode)) { + const proxyDeployerCode = await client.getCode({ address: PROXY_DEPLOYER_ADDRESS }) + const proxyDeployerDeployed = assertStepRuntimeCode(proxyStep, proxyDeployerCode) + return getDeploymentStatusSnapshot(steps, proxyDeployerDeployed ? 1n : 0n, false) + } + const snapshot = getDeploymentStatusSnapshot(steps, await loadDeploymentStatusOracleMask(client), true) + await Promise.all( + snapshot.deploymentStatuses.map(async step => { + if (!step.deployed) return + assertStepRuntimeCode(step, await client.getCode({ address: step.address })) + }), + ) + return snapshot +} diff --git a/ui/statoblast/ts/protocol/index.ts b/ui/statoblast/ts/protocol/index.ts new file mode 100644 index 000000000..cb338ccff --- /dev/null +++ b/ui/statoblast/ts/protocol/index.ts @@ -0,0 +1,14 @@ +export * from './securityPools.js' +export * from './securityVault.js' +export * from './tokenActions.js' +export * from './trading.js' +export * from './truthAuctionActions.js' +export * from './truthAuctions.js' + +export { getDeploymentSteps, loadDeploymentStatusOracleSnapshot, loadErc20Allowance, loadErc20Balance } from './deployment.js' +export { createMarket, loadAllZoltarQuestions, loadMarketDetails, loadZoltarQuestionCount, loadZoltarQuestionPage, loadZoltarUniverseSummary } from '@zoltar/ui-zoltar/protocol/zoltar.js' +export { buildForkCarriedEscalationProofs, loadEscalationDeposits, loadReportingDetails, reportOutcomeInSecurityPool, withdrawEscalationFromSecurityPool, withdrawForkedEscalationDeposits } from '@zoltar/ui-zoltar/protocol/reporting.js' +export { readOptionalMulticall } from '@zoltar/ui-zoltar/protocol/core.js' +export { getMulticall3Address, getOpenOracleAddress, getZoltarAddress } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' +export * from '@zoltar/ui-zoltar/protocol/forks.js' +export * from '@zoltar/ui-zoltar/protocol/openOracle.js' diff --git a/ui/ts/protocol/securityPools.ts b/ui/statoblast/ts/protocol/securityPools.ts similarity index 97% rename from ui/ts/protocol/securityPools.ts rename to ui/statoblast/ts/protocol/securityPools.ts index cf68e0b93..661a3516c 100644 --- a/ui/ts/protocol/securityPools.ts +++ b/ui/statoblast/ts/protocol/securityPools.ts @@ -7,17 +7,17 @@ import { statoblast_factories_SecurityPoolFactory_SecurityPoolFactory, statoblast_tokens_ShareToken_ShareToken, Zoltar_Zoltar, -} from '../contractArtifact.js' -import { isIgnorableLogDecodeError } from '../lib/errors.js' -import { deriveHasForkActivity } from './forkActivity.js' -import { sameAddress } from '../lib/address.js' -import type { ListedSecurityPool, SecurityPoolCreationResult, SecurityPoolPage, SecurityPoolVaultSummary, SecurityVaultDetails, WriteClient, ReadClient } from '../types/contracts.js' -import { readRequiredMulticall, writeContractAndWaitForReceipt } from './core.js' -import { requireForkDataView } from './forkData.js' -import { getForkOutcomeKey, getProtocolPageOffset, getQuestionIdHex, getReportingOutcomeKey, getSecurityPoolSystemState, requireSecurityPoolDeploymentTupleArray, requireSecurityVaultTupleArray } from './helpers.js' +} from '@zoltar/ui-core-shared/contractArtifact.js' +import { isIgnorableLogDecodeError } from '@zoltar/ui-core-shared/lib/errors.js' +import { deriveHasForkActivity } from '@zoltar/ui-zoltar/protocol/forkActivity.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import type { ListedSecurityPool, SecurityPoolCreationResult, SecurityPoolPage, SecurityPoolVaultSummary, SecurityVaultDetails, WriteClient, ReadClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { readRequiredMulticall, writeContractAndWaitForReceipt } from '@zoltar/ui-zoltar/protocol/core.js' +import { requireForkDataView } from '@zoltar/ui-zoltar/protocol/forkData.js' +import { getForkOutcomeKey, getProtocolPageOffset, getQuestionIdHex, getReportingOutcomeKey, getSecurityPoolSystemState, requireSecurityPoolDeploymentTupleArray, requireSecurityVaultTupleArray } from '@zoltar/ui-zoltar/protocol/helpers.js' import { getDeploymentSteps } from './deployment.js' -import { getInfraContractAddresses, getZoltarAddress } from './deploymentHelpers.js' -import { loadMarketDetails } from './zoltar.js' +import { getInfraContractAddresses, getZoltarAddress } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' +import { loadMarketDetails } from '@zoltar/ui-zoltar/protocol/zoltar.js' const QUESTION_OUTCOME_ABI = [ { diff --git a/ui/ts/protocol/securityVault.ts b/ui/statoblast/ts/protocol/securityVault.ts similarity index 87% rename from ui/ts/protocol/securityVault.ts rename to ui/statoblast/ts/protocol/securityVault.ts index 9b326e688..4d936f56b 100644 --- a/ui/ts/protocol/securityVault.ts +++ b/ui/statoblast/ts/protocol/securityVault.ts @@ -1,7 +1,7 @@ import { type Address } from '@zoltar/shared/ethereum' -import { statoblast_SecurityPool_SecurityPool } from '../contractArtifact.js' -import type { SecurityVaultActionResult, WriteClient } from '../types/contracts.js' -import { writeContractAndWait } from './core.js' +import { statoblast_SecurityPool_SecurityPool } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { SecurityVaultActionResult, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { writeContractAndWait } from '@zoltar/ui-zoltar/protocol/core.js' export async function depositRepToVaultToSecurityPool(client: WriteClient, securityPoolAddress: Address, amount: bigint, targetHealthFactorBps = 10_000n) { if (amount <= 0n) throw new Error('REP deposit amount must be greater than zero') diff --git a/ui/ts/protocol/tokenActions.ts b/ui/statoblast/ts/protocol/tokenActions.ts similarity index 72% rename from ui/ts/protocol/tokenActions.ts rename to ui/statoblast/ts/protocol/tokenActions.ts index 2be23b87c..2d7ffdca7 100644 --- a/ui/ts/protocol/tokenActions.ts +++ b/ui/statoblast/ts/protocol/tokenActions.ts @@ -1,7 +1,7 @@ import { type Address } from '@zoltar/shared/ethereum' -import { ABIS } from '../abis.js' -import type { OpenOracleActionResult, SecurityVaultActionResult, WriteClient, ZoltarForkActionResult } from '../types/contracts.js' -import { writeContractAndWait } from './core.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' +import type { OpenOracleActionResult, SecurityVaultActionResult, WriteClient, ZoltarForkActionResult } from '@zoltar/ui-core-shared/types/contracts.js' +import { writeContractAndWait } from '@zoltar/ui-zoltar/protocol/core.js' export async function approveErc20(client: WriteClient, tokenAddress: Address, spenderAddress: Address, amount: bigint, action: Action) { const hash = await writeContractAndWait(client, () => ({ diff --git a/ui/ts/protocol/trading.ts b/ui/statoblast/ts/protocol/trading.ts similarity index 94% rename from ui/ts/protocol/trading.ts rename to ui/statoblast/ts/protocol/trading.ts index dad3d4f55..06f3ee619 100644 --- a/ui/ts/protocol/trading.ts +++ b/ui/statoblast/ts/protocol/trading.ts @@ -1,11 +1,11 @@ import { type Address, type TransactionReceipt } from '@zoltar/shared/ethereum' import { sortBigIntsAscending } from '@zoltar/shared/bigInt' -import { assertNever } from '../lib/assert.js' -import { statoblast_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator, statoblast_SecurityPool_SecurityPool, statoblast_tokens_ShareToken_ShareToken, ZoltarQuestionData_ZoltarQuestionData } from '../contractArtifact.js' -import type { ReadClient, ReportingOutcomeKey, TradingActionResult, TradingDetails, TradingShareBalances, WriteClient } from '../types/contracts.js' -import { getMinBigintValue, isBigintTriple } from './helpers.js' -import { type WriteContractClient, readRequiredMulticall, writeContractAndWait } from './core.js' -import { readSecurityPoolUniverseId } from './securityPoolActions.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { statoblast_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator, statoblast_SecurityPool_SecurityPool, statoblast_tokens_ShareToken_ShareToken, ZoltarQuestionData_ZoltarQuestionData } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { ReadClient, ReportingOutcomeKey, TradingActionResult, TradingDetails, TradingShareBalances, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { getMinBigintValue, isBigintTriple } from '@zoltar/ui-zoltar/protocol/helpers.js' +import { type WriteContractClient, readRequiredMulticall, writeContractAndWait } from '@zoltar/ui-zoltar/protocol/core.js' +import { readSecurityPoolUniverseId } from '@zoltar/ui-core-shared/protocol/securityPoolActions.js' type ReadWriteContractClient = TransactionReceipt> = Pick & WriteContractClient type SecurityPoolMintCapacity = { diff --git a/ui/ts/protocol/truthAuctionActions.ts b/ui/statoblast/ts/protocol/truthAuctionActions.ts similarity index 89% rename from ui/ts/protocol/truthAuctionActions.ts rename to ui/statoblast/ts/protocol/truthAuctionActions.ts index e42652ab5..17ba18ed0 100644 --- a/ui/ts/protocol/truthAuctionActions.ts +++ b/ui/statoblast/ts/protocol/truthAuctionActions.ts @@ -1,9 +1,9 @@ import { type Address } from '@zoltar/shared/ethereum' -import { statoblast_SecurityPoolForker_SecurityPoolForker, statoblast_UniformPriceDualCapBatchAuction_UniformPriceDualCapBatchAuction } from '../contractArtifact.js' -import type { WriteClient } from '../types/contracts.js' -import { writeContractAndWait } from './core.js' -import { getInfraContractAddresses } from './deploymentHelpers.js' -import { executeForkAuctionAction } from './securityPoolActions.js' +import { statoblast_SecurityPoolForker_SecurityPoolForker, statoblast_UniformPriceDualCapBatchAuction_UniformPriceDualCapBatchAuction } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { writeContractAndWait } from '@zoltar/ui-zoltar/protocol/core.js' +import { getInfraContractAddresses } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' +import { executeForkAuctionAction } from '@zoltar/ui-core-shared/protocol/securityPoolActions.js' export async function startTruthAuctionForSecurityPool(client: WriteClient, securityPoolAddress: Address, universeId: bigint) { return await executeForkAuctionAction( diff --git a/ui/ts/protocol/truthAuctions.ts b/ui/statoblast/ts/protocol/truthAuctions.ts similarity index 95% rename from ui/ts/protocol/truthAuctions.ts rename to ui/statoblast/ts/protocol/truthAuctions.ts index 173ce9e18..55bc21115 100644 --- a/ui/ts/protocol/truthAuctions.ts +++ b/ui/statoblast/ts/protocol/truthAuctions.ts @@ -1,8 +1,8 @@ import type { Address } from '@zoltar/shared/ethereum' -import { statoblast_UniformPriceDualCapBatchAuction_UniformPriceDualCapBatchAuction } from '../contractArtifact.js' -import type { ReadClient, TruthAuctionBidView, TruthAuctionBidderBidPage, TruthAuctionTickBidPage, TruthAuctionTickPage, TruthAuctionTickSummary } from '../types/contracts.js' -import { requireAddressValue, requireArrayValue, requireBigintValue, requireBooleanValue, requireObjectValue } from './decoders.js' -import { getProtocolPageOffset } from './helpers.js' +import { statoblast_UniformPriceDualCapBatchAuction_UniformPriceDualCapBatchAuction } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { ReadClient, TruthAuctionBidView, TruthAuctionBidderBidPage, TruthAuctionTickBidPage, TruthAuctionTickPage, TruthAuctionTickSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { requireAddressValue, requireArrayValue, requireBigintValue, requireBooleanValue, requireObjectValue } from '@zoltar/ui-zoltar/protocol/decoders.js' +import { getProtocolPageOffset } from '@zoltar/ui-zoltar/protocol/helpers.js' function requireTruthAuctionTickSummary(value: unknown, context: string): TruthAuctionTickSummary { const summary = requireObjectValue(value, context) diff --git a/ui/statoblast/ts/simulation/index.ts b/ui/statoblast/ts/simulation/index.ts new file mode 100644 index 000000000..cc1e748b4 --- /dev/null +++ b/ui/statoblast/ts/simulation/index.ts @@ -0,0 +1,19 @@ +import { registerSimulationScenario } from '@zoltar/ui-core-shared/simulation/scenarios.js' +import { getStatoblastScenarioDescription, getStatoblastScenarioLabel, type StatoblastScenario } from './statoblastScenarios.js' + +const STATOBLAST_SCENARIOS = ['security-pool', 'securitypoolx2', 'securitypoolx2-auction'] as const satisfies readonly StatoblastScenario[] + +export function isStatoblastScenario(value: string): value is StatoblastScenario { + return (STATOBLAST_SCENARIOS as readonly string[]).includes(value) +} + +export { getStatoblastScenarioDescription, getStatoblastScenarioLabel } + +export function registerStatoblastSimulationScenarios() { + for (const scenario of STATOBLAST_SCENARIOS) { + registerSimulationScenario(scenario, { + description: getStatoblastScenarioDescription(scenario), + label: getStatoblastScenarioLabel(scenario), + }) + } +} diff --git a/ui/ts/simulation/bootstrap.ts b/ui/statoblast/ts/simulation/statoblastScenarios.ts similarity index 53% rename from ui/ts/simulation/bootstrap.ts rename to ui/statoblast/ts/simulation/statoblastScenarios.ts index a0535a7da..61fa30c80 100644 --- a/ui/ts/simulation/bootstrap.ts +++ b/ui/statoblast/ts/simulation/statoblastScenarios.ts @@ -1,51 +1,84 @@ -import { createMemoryClient } from 'tevm' -import { encodeAbiParameters, encodeDeployData, getCreateAddress, keccak256, toHex, zeroAddress, type Address, type Hex } from '@zoltar/shared/ethereum' +import { zeroAddress, type Address } from '@zoltar/shared/ethereum' import { DEFAULT_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS } from '@zoltar/shared/oracleInitialReport' -import { - approveErc20, - createMarket, - createChildUniverseFromSecurityPool, - createCompleteSetInSecurityPool, - createSecurityPool, - depositRepToVaultToSecurityPool, - forkZoltarWithOwnEscalation, - getDeploymentSteps, - loadAllSecurityPools, - loadForkAuctionDetails, - loadOracleManagerDetails, - loadOpenOracleReportDetails, - loadReportingDetails, - loadSecurityVaultDetails, - loadZoltarUniverseSummary, - migrateRepToZoltarFromSecurityPool, - reportOutcomeInSecurityPool, - requestOraclePrice, - settleOracleReport, - startTruthAuctionForSecurityPool, - submitTruthAuctionBid, -} from '../protocol/index.js' -import { ReputationToken_ReputationToken, Zoltar_Zoltar, statoblast_WETH9_WETH9 } from '../contractArtifact.js' -import { assertNever } from '../lib/assert.js' -import { getTruthAuctionPriceAtTick, getTruthAuctionTickAtPrice } from '../protocol/truthAuctionMath.js' -import type { ReadClient, WriteClient } from '../lib/chainBackend.js' -import { MAINNET_WETH_ADDRESS, setRuntimeNetworkProfile, type NetworkProfile } from '../lib/networkProfile.js' -import type { ListedSecurityPool, QuestionData } from '../types/contracts.js' -import { advanceSimulationTime, getSimulationChainTimestamp, initializeSimulationClock } from './clock.js' -import type { SimulationScenario } from './scenarios.js' +import * as protocol from '../protocol/index.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' + +type StatoblastScenarioProtocol = Pick< + typeof protocol, + | 'approveErc20' + | 'createChildUniverseFromSecurityPool' + | 'createCompleteSetInSecurityPool' + | 'createMarket' + | 'createSecurityPool' + | 'depositRepToVaultToSecurityPool' + | 'forkZoltarWithOwnEscalation' + | 'getDeploymentSteps' + | 'loadAllSecurityPools' + | 'loadForkAuctionDetails' + | 'loadOracleManagerDetails' + | 'loadOpenOracleReportDetails' + | 'loadReportingDetails' + | 'loadSecurityVaultDetails' + | 'loadZoltarUniverseSummary' + | 'migrateRepToZoltarFromSecurityPool' + | 'reportOutcomeInSecurityPool' + | 'requestOraclePrice' + | 'settleOracleReport' + | 'startTruthAuctionForSecurityPool' + | 'submitTruthAuctionBid' +> + +let scenarioProtocolOverride: StatoblastScenarioProtocol | undefined + +export function installStatoblastScenarioProtocolForTesting(override: StatoblastScenarioProtocol | undefined) { + scenarioProtocolOverride = override +} + +function getScenarioProtocol(): StatoblastScenarioProtocol { + return scenarioProtocolOverride ?? protocol +} +import { createRangeProgressReporter, deploySimulationAppContracts, reportBootstrapProgress, requireQaAccount, type BootstrapProgressHandler, type ProgressRange, type BootstrapScenarioApplyParameters, type TevmLikeClient } from '@zoltar/ui-core-shared/simulation/bootstrap.js' +import { getTruthAuctionPriceAtTick, getTruthAuctionTickAtPrice } from '@zoltar/ui-core-shared/protocol/truthAuctionMath.js' +import { advanceSimulationTime, getSimulationChainTimestamp } from '@zoltar/ui-core-shared/simulation/clock.js' +import type { ReadClient, WriteClient } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import type { NetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import type { ListedSecurityPool, QuestionData } from '@zoltar/ui-core-shared/types/contracts.js' + +export type StatoblastScenario = 'security-pool' | 'securitypoolx2' | 'securitypoolx2-auction' + +export function getStatoblastScenarioLabel(scenario: StatoblastScenario) { + switch (scenario) { + case 'security-pool': + return 'Security pool' + case 'securitypoolx2': + return 'Security pool x2' + case 'securitypoolx2-auction': + return 'Security pool x2 auction' + default: + return assertNever(scenario) + } +} -type TevmLikeClient = ReturnType +export function getStatoblastScenarioDescription(scenario: StatoblastScenario) { + switch (scenario) { + case 'security-pool': + return 'One seeded question, one security pool, and one funded vault with an active capacity ownership. Use it to test pool actions and liquidation paths.' + case 'securitypoolx2': + return 'Two seeded questions with two security pools and two funded vaults in each pool. Use it to test multi-pool selection and repeated pool actions.' + case 'securitypoolx2-auction': + return 'Two seeded questions with one own-escalation fork already triggered and one child truth auction seeded with ten bids. Use it to test the fork-auction bidbook and settlement actions.' + default: + return assertNever(scenario) + } +} + +const DAY_IN_SECONDS = 24n * 60n * 60n function getSeededCoordinatorInitialReportPrice() { return SEEDED_REP_ETH_PRICE } -type BootstrapProgressHandler = (progress: { label: string; value: number }) => Promise | void - -const DAY_IN_SECONDS = 24n * 60n * 60n const FORK_MIGRATION_TIME_SECONDS = 8n * 7n * DAY_IN_SECONDS -const ETH_BALANCE_AMOUNT = 10n ** 30n -const GENESIS_UNIVERSE_ID = 0n const SEEDED_REP_ETH_PRICE = 3n * 10n ** 18n -const REP_TOKEN_MINT_AMOUNT = 3_000_000n * 10n ** 18n const STATOBLAST_SECURITY_MULTIPLIER_BPS = 20_000n const SECURITY_POOL_REP_DEPOSIT = 10_000n * 10n ** 18n const CAPACITY_OWNERSHIP_ATTO_REP = 80n * 10n ** 18n @@ -57,318 +90,6 @@ const SECURITY_POOL_X2_AUCTION_EXTRA_REP_DEPOSIT = 2_000_000n * 10n ** 18n const SECURITY_POOL_X2_AUCTION_UNMIGRATED_REP_DEPOSIT = 1_000n * 10n ** 18n const SECURITY_POOL_X2_AUCTION_BID_PRICES = [getTruthAuctionPriceAtTick(12n), getTruthAuctionPriceAtTick(10n), getTruthAuctionPriceAtTick(8n)] as const const SECURITY_POOL_X2_AUCTION_BID_AMOUNTS = [3n * 10n ** 18n, 4n * 10n ** 18n, 5n * 10n ** 18n, 6n * 10n ** 18n, 3n * 10n ** 18n, 4n * 10n ** 18n, 5n * 10n ** 18n, 3n * 10n ** 18n, 4n * 10n ** 18n, 5n * 10n ** 18n] as const -const WETH_TOKEN_MINT_AMOUNT = 10_000n * 10n ** 18n -const WETH_NAME_SLOT = 0n -const WETH_SYMBOL_SLOT = 1n -const WETH_DECIMALS_SLOT = 2n -const ZOLTAR_GENESIS_REPUTATION_TOKEN_OFFSET = 3n -const ZOLTAR_UNIVERSE_THEORETICAL_SUPPLIES_SLOT = 2n -const ZOLTAR_UNIVERSES_SLOT = 0n - -async function yieldToBrowser() { - await new Promise(resolve => { - setTimeout(resolve, 0) - }) -} - -async function withTimeout(work: Promise, timeoutMilliseconds: number, message: string) { - let timeoutId: ReturnType | undefined = undefined - try { - return await Promise.race([ - work, - new Promise((_, reject) => { - timeoutId = setTimeout(() => { - reject(new Error(message)) - }, timeoutMilliseconds) - }), - ]) - } finally { - if (timeoutId !== undefined) clearTimeout(timeoutId) - } -} - -function clampProgress(value: number) { - return Math.max(0, Math.min(1, value)) -} - -async function reportBootstrapProgress(onProgress: BootstrapProgressHandler | undefined, label: string, value: number) { - await onProgress?.({ - label, - value: clampProgress(value), - }) - await yieldToBrowser() -} - -function storageIndex(slot: bigint) { - return toHex(slot, { size: 32 }) -} - -function storageValue(value: bigint) { - return toHex(value, { size: 32 }) -} - -function shortStringStorageValue(value: string) { - const valueHex = toHex(value).slice(2) - const byteLength = valueHex.length / 2 - if (byteLength > 31) throw new Error('Simulation token metadata exceeds Solidity short-string storage') - return storageValue(BigInt(`0x${valueHex.padEnd(62, '0')}${(byteLength * 2).toString(16).padStart(2, '0')}`)) -} - -function requireReceiptContractAddress(code: Hex | undefined, address: Address, label: string) { - if (code === undefined || code === '0x') throw new Error(`Failed to deploy ${label} at ${address}`) -} - -async function deployContract(writeClient: WriteClient, address: Address, label: string, data: Hex) { - const hash = await writeClient.sendTransaction({ data }) - await writeClient.waitForTransactionReceipt({ hash }) - const code = await writeClient.getCode({ address }) - requireReceiptContractAddress(code, address, label) -} - -function getZoltarUniverseBaseSlot(universeId: bigint) { - return BigInt(keccak256(encodeAbiParameters([{ type: 'uint248' }, { type: 'uint256' }], [universeId, ZOLTAR_UNIVERSES_SLOT]))) -} - -function getZoltarUniverseTheoreticalSupplyAttoRepSlot(universeId: bigint) { - return BigInt(keccak256(encodeAbiParameters([{ type: 'uint248' }, { type: 'uint256' }], [universeId, ZOLTAR_UNIVERSE_THEORETICAL_SUPPLIES_SLOT]))) -} - -async function seedAccountBalances(memoryClient: TevmLikeClient, accounts: readonly Address[], onProgress?: BootstrapProgressHandler) { - for (const [index, account] of accounts.entries()) { - await memoryClient.impersonateAccount({ address: account }) - await memoryClient.setBalance({ address: account, value: ETH_BALANCE_AMOUNT }) - await reportBootstrapProgress(onProgress, `Funding QA account ${index + 1} of ${accounts.length}`, 0.05 + ((index + 1) / Math.max(accounts.length, 1)) * 0.08) - } -} - -async function withSimulationAuthorityAccount(memoryClient: TevmLikeClient, accountAddress: Address, work: () => Promise) { - const originalBalance = await memoryClient.getBalance({ address: accountAddress }) - await memoryClient.impersonateAccount({ address: accountAddress }) - await memoryClient.setBalance({ address: accountAddress, value: ETH_BALANCE_AMOUNT }) - try { - return await work() - } finally { - await memoryClient.setBalance({ address: accountAddress, value: originalBalance }) - } -} - -async function seedGenesisRepTokenState({ - accounts, - createWriteClient, - memoryClient, - onProgress, - repAddress, - zoltarAddress, -}: { - accounts: readonly Address[] - createWriteClient: (accountAddress: Address) => WriteClient - memoryClient: TevmLikeClient - onProgress: BootstrapProgressHandler | undefined - repAddress: Address - zoltarAddress: Address -}) { - const originalNonce = await memoryClient.getTransactionCount({ address: zoltarAddress }) - try { - return await withSimulationAuthorityAccount(memoryClient, zoltarAddress, async () => { - const zoltarWriteClient = createWriteClient(zoltarAddress) - const totalSupply = BigInt(accounts.length) * REP_TOKEN_MINT_AMOUNT - const syncHash = await zoltarWriteClient.writeContract({ - address: repAddress, - abi: ReputationToken_ReputationToken.abi, - functionName: 'setMaxTheoreticalSupplyAttoRep', - args: [totalSupply], - }) - await zoltarWriteClient.waitForTransactionReceipt({ hash: syncHash }) - for (const [index, account] of accounts.entries()) { - const hash = await zoltarWriteClient.writeContract({ - address: repAddress, - abi: ReputationToken_ReputationToken.abi, - functionName: 'mint', - args: [account, REP_TOKEN_MINT_AMOUNT], - }) - await zoltarWriteClient.waitForTransactionReceipt({ hash }) - await reportBootstrapProgress(onProgress, `Seeding REP balances ${index + 1} of ${accounts.length}`, 0.16 + ((index + 1) / Math.max(accounts.length, 1)) * 0.06) - } - await reportBootstrapProgress(onProgress, 'Finalizing REP token state', 0.23) - return totalSupply - }) - } finally { - await memoryClient.setNonce({ address: zoltarAddress, nonce: originalNonce }) - } -} - -export async function updateZoltarGenesisRepToken({ createWriteClient, memoryClient, repAddress, zoltarAddress }: { createWriteClient: (accountAddress: Address) => WriteClient; memoryClient: TevmLikeClient; repAddress: Address; zoltarAddress: Address }) { - const universeBaseSlot = getZoltarUniverseBaseSlot(GENESIS_UNIVERSE_ID) - const readClient = createWriteClient(zoltarAddress) - const genesisTheoreticalSupply = await readClient.readContract({ - address: repAddress, - abi: ReputationToken_ReputationToken.abi, - functionName: 'getTotalTheoreticalSupplyAttoRep', - args: [], - }) - - await memoryClient.setStorageAt({ - address: zoltarAddress, - index: storageIndex(universeBaseSlot + ZOLTAR_GENESIS_REPUTATION_TOKEN_OFFSET), - value: storageValue(BigInt(repAddress)), - }) - await memoryClient.setStorageAt({ - address: zoltarAddress, - index: storageIndex(getZoltarUniverseTheoreticalSupplyAttoRepSlot(GENESIS_UNIVERSE_ID)), - value: storageValue(genesisTheoreticalSupply), - }) - - const patchedRepToken = await readClient.readContract({ - address: zoltarAddress, - abi: Zoltar_Zoltar.abi, - functionName: 'getRepToken', - args: [GENESIS_UNIVERSE_ID], - }) - if (patchedRepToken.toLowerCase() !== repAddress.toLowerCase()) { - throw new Error(`Failed to patch simulation Zoltar genesis REP token. Expected ${repAddress}, received ${patchedRepToken}.`) - } - - const patchedTheoreticalSupply = await readClient.readContract({ - address: zoltarAddress, - abi: Zoltar_Zoltar.abi, - functionName: 'getUniverseTheoreticalSupplyAttoRep', - args: [GENESIS_UNIVERSE_ID], - }) - if (patchedTheoreticalSupply !== genesisTheoreticalSupply) { - throw new Error(`Failed to patch simulation Zoltar theoretical supply. Expected ${genesisTheoreticalSupply.toString()}, received ${patchedTheoreticalSupply.toString()}.`) - } -} - -export async function mintSimulationGenesisRep({ accountAddress, amount, createWriteClient, memoryClient, repAddress, zoltarAddress }: { accountAddress: Address; amount: bigint; createWriteClient: (accountAddress: Address) => WriteClient; memoryClient: TevmLikeClient; repAddress: Address; zoltarAddress: Address }) { - if (amount <= 0n) { - throw new Error('Simulation REP mint amount must be greater than zero') - } - - const originalNonce = await memoryClient.getTransactionCount({ address: zoltarAddress }) - try { - await withSimulationAuthorityAccount(memoryClient, zoltarAddress, async () => { - const zoltarWriteClient = createWriteClient(zoltarAddress) - const totalSupply = await zoltarWriteClient.readContract({ - address: repAddress, - abi: ReputationToken_ReputationToken.abi, - functionName: 'totalSupply', - args: [], - }) - const syncHash = await zoltarWriteClient.writeContract({ - address: repAddress, - abi: ReputationToken_ReputationToken.abi, - functionName: 'setMaxTheoreticalSupplyAttoRep', - args: [totalSupply + amount], - }) - await zoltarWriteClient.waitForTransactionReceipt({ hash: syncHash }) - const mintHash = await zoltarWriteClient.writeContract({ - address: repAddress, - abi: ReputationToken_ReputationToken.abi, - functionName: 'mint', - args: [accountAddress, amount], - }) - await zoltarWriteClient.waitForTransactionReceipt({ hash: mintHash }) - - const zoltarCode = await memoryClient.getCode({ - address: zoltarAddress, - }) - if (zoltarCode === undefined || zoltarCode === '0x') { - return - } - - await updateZoltarGenesisRepToken({ - createWriteClient, - memoryClient, - repAddress, - zoltarAddress, - }) - }) - } finally { - await memoryClient.setNonce({ address: zoltarAddress, nonce: originalNonce }) - } -} - -async function deploySimulationTokens({ - accounts, - createWriteClient, - memoryClient, - onProgress, - primaryAccount, - profile, - zoltarAddress, -}: { - accounts: readonly Address[] - createWriteClient: (accountAddress: Address) => WriteClient - memoryClient: TevmLikeClient - onProgress: BootstrapProgressHandler | undefined - primaryAccount: Address - profile: NetworkProfile - zoltarAddress: Address -}) { - const writeClient = createWriteClient(primaryAccount) - const repDeploymentData = encodeDeployData({ - abi: ReputationToken_ReputationToken.abi, - args: [zoltarAddress], - bytecode: `0x${ReputationToken_ReputationToken.evm.bytecode.object}`, - }) - - await deployContract(writeClient, profile.genesisRepTokenAddress, 'simulation REP token', repDeploymentData) - await reportBootstrapProgress(onProgress, 'Deploying simulation REP token', 0.18) - await memoryClient.setCode({ - address: profile.wethAddress, - bytecode: `0x${statoblast_WETH9_WETH9.evm.deployedBytecode.object}`, - }) - await memoryClient.setStorageAt({ address: profile.wethAddress, index: storageIndex(WETH_NAME_SLOT), value: shortStringStorageValue('Wrapped Ether') }) - await memoryClient.setStorageAt({ address: profile.wethAddress, index: storageIndex(WETH_SYMBOL_SLOT), value: shortStringStorageValue('WETH') }) - await memoryClient.setStorageAt({ address: profile.wethAddress, index: storageIndex(WETH_DECIMALS_SLOT), value: storageValue(18n) }) - await reportBootstrapProgress(onProgress, 'Installing simulation WETH token', 0.2) - await seedGenesisRepTokenState({ - accounts, - createWriteClient, - memoryClient, - onProgress, - repAddress: profile.genesisRepTokenAddress, - zoltarAddress, - }) -} - -export function predictSimulationTokenAddresses(accountAddress: Address): { genesisRepTokenAddress: Address; wethAddress: Address } { - return { - genesisRepTokenAddress: getCreateAddress({ from: accountAddress, nonce: 0n }), - wethAddress: MAINNET_WETH_ADDRESS, - } -} - -async function seedWrappedEthBalances(createWriteClient: (accountAddress: Address) => WriteClient, accounts: readonly Address[], wethAddress: Address, onProgress: BootstrapProgressHandler | undefined) { - for (const [index, account] of accounts.entries()) { - const writeClient = createWriteClient(account) - const hash = await writeClient.sendTransaction({ - to: wethAddress, - value: WETH_TOKEN_MINT_AMOUNT, - }) - await writeClient.waitForTransactionReceipt({ hash }) - await reportBootstrapProgress(onProgress, `Wrapping ETH for QA account ${index + 1} of ${accounts.length}`, 0.24 + ((index + 1) / Math.max(accounts.length, 1)) * 0.06) - } -} - -async function deploySimulationAppContracts(primaryWriteClient: WriteClient, memoryClient: TevmLikeClient, onProgress: BootstrapProgressHandler | undefined, profile: NetworkProfile, range: { start: number; end: number } = { start: 0.32, end: 0.8 }) { - const steps = getDeploymentSteps(profile) - for (const [index, step] of steps.entries()) { - const code = await memoryClient.getCode({ address: step.address }) - if (code !== undefined && code !== '0x') { - await reportBootstrapProgress(onProgress, `Checking ${step.label}`, range.start + ((index + 1) / Math.max(steps.length, 1)) * (range.end - range.start)) - continue - } - await step.deploy(primaryWriteClient) - await reportBootstrapProgress(onProgress, `Deploying ${step.label}`, range.start + ((index + 1) / Math.max(steps.length, 1)) * (range.end - range.start)) - } -} - -type ProgressRange = { - end: number - start: number -} - type SeededVaultSpec = { accountAddress: Address vaultRepBackingDepositAttoRep: bigint @@ -383,20 +104,6 @@ type SeededSecurityPoolSpec = { vaults: readonly SeededVaultSpec[] } -function createRangeProgressReporter(onProgress: BootstrapProgressHandler | undefined, range: ProgressRange, stepCount: number) { - let completedStepCount = 0 - - return async (label: string) => { - completedStepCount += 1 - await reportBootstrapProgress(onProgress, label, range.start + (completedStepCount / Math.max(stepCount, 1)) * (range.end - range.start)) - } -} - -function requireQaAccount(account: Address | undefined, label: string) { - if (account === undefined) throw new Error(label) - return account -} - function createSecurityPoolSeedParameters( currentTimestamp: bigint, title: string, @@ -422,13 +129,13 @@ function createSecurityPoolSeedParameters( } async function loadRequiredSeededPool(readClient: ReadClient, securityPoolAddress: Address, poolLabel: string) { - const seededPool = (await loadAllSecurityPools(readClient)).find(pool => pool.securityPoolAddress === securityPoolAddress) + const seededPool = (await getScenarioProtocol().loadAllSecurityPools(readClient)).find(pool => pool.securityPoolAddress === securityPoolAddress) if (seededPool === undefined) throw new Error(`Expected ${poolLabel} at ${securityPoolAddress}`) return seededPool } async function loadRequiredSecurityVault(readClient: ReadClient, securityPoolAddress: Address, vaultAddress: Address, label: string) { - const vaultDetails = await loadSecurityVaultDetails(readClient, securityPoolAddress, vaultAddress) + const vaultDetails = await getScenarioProtocol().loadSecurityVaultDetails(readClient, securityPoolAddress, vaultAddress) if (vaultDetails === undefined) throw new Error(`Expected seeded security vault details for ${label}`) return vaultDetails } @@ -444,9 +151,9 @@ function getSeededVaultDepositTargetFactorBps(vault: SeededVaultSpec) { async function createSeededSecurityPool({ createWriteClient, currentTimestamp, deployerAccount, questionTitle }: { createWriteClient: (accountAddress: Address) => WriteClient; currentTimestamp: bigint; deployerAccount: Address; questionTitle: string }) { const deployerWriteClient = createWriteClient(deployerAccount) - const marketResult = await createMarket(deployerWriteClient, createSecurityPoolSeedParameters(currentTimestamp, questionTitle)) + const marketResult = await getScenarioProtocol().createMarket(deployerWriteClient, createSecurityPoolSeedParameters(currentTimestamp, questionTitle)) const questionId = BigInt(marketResult.questionId) - const poolResult = await createSecurityPool(deployerWriteClient, { + const poolResult = await getScenarioProtocol().createSecurityPool(deployerWriteClient, { initialReportPriorityFeeAttoEthPerGas: DEFAULT_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS, questionId, statoblastSecurityMultiplierBps: STATOBLAST_SECURITY_MULTIPLIER_BPS, @@ -498,10 +205,10 @@ async function settleSeededOracleReport({ }) { const writeClient = createWriteClient(accountAddress) const initialReportPrice = getSeededCoordinatorInitialReportPrice() - await requestOraclePrice(writeClient, managerAddress, initialReportPrice) + await getScenarioProtocol().requestOraclePrice(writeClient, managerAddress, initialReportPrice) await onProgressStep(`Configuring oracle manager for ${poolLabel}`) - const oracleManagerDetails = await loadOracleManagerDetails(readClient, managerAddress) + const oracleManagerDetails = await getScenarioProtocol().loadOracleManagerDetails(readClient, managerAddress) if (oracleManagerDetails.pendingReportId === 0n) throw new Error(`Expected a pending oracle report for ${poolLabel}`) await onProgressStep(`Opening seeded oracle report for ${poolLabel}`) @@ -512,7 +219,7 @@ async function settleSeededOracleReport({ } async function settleOracleReportIfNeeded({ memoryClient, readClient, writeClient, openOracleAddress, pendingReportId }: { memoryClient: TevmLikeClient; readClient: ReadClient; writeClient: WriteClient; openOracleAddress: Address; pendingReportId: bigint }) { - const seededReport = await loadOpenOracleReportDetails(readClient, openOracleAddress, pendingReportId) + const seededReport = await getScenarioProtocol().loadOpenOracleReportDetails(readClient, openOracleAddress, pendingReportId) if (seededReport.isDistributed) return const reportTimestamp = getSimulationReportTiming(seededReport.reportTimestamp) const settlementTime = getSimulationReportTiming(seededReport.settlementTime) @@ -523,22 +230,22 @@ async function settleOracleReportIfNeeded({ memoryClient, readClient, writeClien await advanceSimulationTime(memoryClient, settlementReadyTimestamp - currentTimestamp) } } - await settleOracleReport(writeClient, openOracleAddress, pendingReportId) + await getScenarioProtocol().settleOracleReport(writeClient, openOracleAddress, pendingReportId) } async function refreshSeededOraclePrice({ accountAddress, createWriteClient, managerAddress, memoryClient, readClient }: { accountAddress: Address; createWriteClient: (accountAddress: Address) => WriteClient; managerAddress: Address; memoryClient: TevmLikeClient; readClient: ReadClient }) { const writeClient = createWriteClient(accountAddress) - let managerDetails = await loadOracleManagerDetails(readClient, managerAddress) + let managerDetails = await getScenarioProtocol().loadOracleManagerDetails(readClient, managerAddress) if (managerDetails.isPriceValid) return if (managerDetails.pendingReportId === 0n) { const initialReportPrice = getSeededCoordinatorInitialReportPrice() - await requestOraclePrice(writeClient, managerAddress, initialReportPrice) - managerDetails = await loadOracleManagerDetails(readClient, managerAddress) + await getScenarioProtocol().requestOraclePrice(writeClient, managerAddress, initialReportPrice) + managerDetails = await getScenarioProtocol().loadOracleManagerDetails(readClient, managerAddress) } if (managerDetails.pendingReportId === 0n) { throw new Error(`Expected a pending oracle report for ${managerAddress}`) } - const reportDetails = await loadOpenOracleReportDetails(readClient, managerDetails.openOracleAddress, managerDetails.pendingReportId) + const reportDetails = await getScenarioProtocol().loadOpenOracleReportDetails(readClient, managerDetails.openOracleAddress, managerDetails.pendingReportId) if (reportDetails.reportTimestamp === 0n || reportDetails.currentReporter === zeroAddress) { throw new Error(`Expected the coordinator request to submit the initial report for ${managerAddress}`) } @@ -549,7 +256,7 @@ async function refreshSeededOraclePrice({ accountAddress, createWriteClient, man readClient, writeClient, }) - const refreshedManagerDetails = await loadOracleManagerDetails(readClient, managerAddress) + const refreshedManagerDetails = await getScenarioProtocol().loadOracleManagerDetails(readClient, managerAddress) if (!refreshedManagerDetails.isPriceValid) throw new Error(`Expected a valid seeded oracle price for ${managerAddress}`) } @@ -595,8 +302,8 @@ async function seedSecurityPool({ for (const [index, vaultSpec] of poolSpec.vaults.entries()) { const writeClient = createWriteClient(vaultSpec.accountAddress) - await approveErc20(writeClient, profile.genesisRepTokenAddress, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, 'approveRep') - await depositRepToVaultToSecurityPool(writeClient, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, getSeededVaultDepositTargetFactorBps(vaultSpec)) + await getScenarioProtocol().approveErc20(writeClient, profile.genesisRepTokenAddress, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, 'approveRep') + await getScenarioProtocol().depositRepToVaultToSecurityPool(writeClient, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, getSeededVaultDepositTargetFactorBps(vaultSpec)) const seededVault = await loadRequiredSecurityVault(readClient, poolResult.securityPoolAddress, vaultSpec.accountAddress, vaultSpec.accountAddress) if (seededVault.vaultAttoRepBacking !== vaultSpec.vaultRepBackingDepositAttoRep) throw new Error(`Expected seeded REP deposit for ${vaultSpec.accountAddress} in ${poolSpec.poolLabel}, got ${seededVault.vaultAttoRepBacking.toString()}`) await reportStep(`Funding seeded security vault ${index + 1} of ${poolSpec.vaults.length} for ${poolSpec.poolLabel}`) @@ -620,7 +327,7 @@ async function seedSecurityPool({ }) await reportStep(`Settling seeded oracle report for ${poolSpec.poolLabel}`) - const seededReport = await loadOpenOracleReportDetails(readClient, seededOracleReport.openOracleAddress, seededOracleReport.pendingReportId) + const seededReport = await getScenarioProtocol().loadOpenOracleReportDetails(readClient, seededOracleReport.openOracleAddress, seededOracleReport.pendingReportId) if (!seededReport.isDistributed) throw new Error(`Expected the seeded oracle report to be settled for ${poolSpec.poolLabel}`) const primaryVaultAfterSettlement = await loadRequiredSecurityVault(readClient, poolResult.securityPoolAddress, primaryVaultAccount, primaryVaultAccount) @@ -749,8 +456,8 @@ async function seedSecurityPoolX2Scenario({ for (const [index, vaultSpec] of seededPool.vaults.entries()) { const writeClient = createWriteClient(vaultSpec.accountAddress) - await approveErc20(writeClient, profile.genesisRepTokenAddress, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, 'approveRep') - await depositRepToVaultToSecurityPool(writeClient, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, getSeededVaultDepositTargetFactorBps(vaultSpec)) + await getScenarioProtocol().approveErc20(writeClient, profile.genesisRepTokenAddress, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, 'approveRep') + await getScenarioProtocol().depositRepToVaultToSecurityPool(writeClient, poolResult.securityPoolAddress, vaultSpec.vaultRepBackingDepositAttoRep, getSeededVaultDepositTargetFactorBps(vaultSpec)) const seededVault = await loadRequiredSecurityVault(readClient, poolResult.securityPoolAddress, vaultSpec.accountAddress, vaultSpec.accountAddress) if (seededVault.vaultAttoRepBacking !== vaultSpec.vaultRepBackingDepositAttoRep) throw new Error(`Expected seeded REP deposit for ${vaultSpec.accountAddress} in ${seededPool.poolLabel}, got ${seededVault.vaultAttoRepBacking.toString()}`) await reportStep(`Funding seeded security vault ${index + 1} of ${seededPool.vaults.length} for ${seededPool.poolLabel}`) @@ -789,7 +496,7 @@ async function seedSecurityPoolX2Scenario({ }) await reportStep(`Settling seeded oracle report for ${preparedPool.poolLabel}`) - const seededReport = await loadOpenOracleReportDetails(readClient, preparedPool.openOracleAddress, preparedPool.pendingReportId) + const seededReport = await getScenarioProtocol().loadOpenOracleReportDetails(readClient, preparedPool.openOracleAddress, preparedPool.pendingReportId) if (!seededReport.isDistributed) throw new Error(`Expected the seeded oracle report to be settled for ${preparedPool.poolLabel}`) const primaryVaultAfterSettlement = await loadRequiredSecurityVault(readClient, preparedPool.securityPoolAddress, primaryAccount, primaryAccount) @@ -815,7 +522,7 @@ async function seedSecurityPoolX2Scenario({ } async function loadRequiredChildSecurityPool(readClient: ReadClient, parentSecurityPoolAddress: Address, questionOutcome: ListedSecurityPool['questionOutcome']) { - const childPool = (await loadAllSecurityPools(readClient)).find(pool => pool.parent === parentSecurityPoolAddress && pool.questionOutcome === questionOutcome) + const childPool = (await getScenarioProtocol().loadAllSecurityPools(readClient)).find(pool => pool.parent === parentSecurityPoolAddress && pool.questionOutcome === questionOutcome) if (childPool === undefined) throw new Error(`Expected a ${questionOutcome} child pool for ${parentSecurityPoolAddress}`) return childPool } @@ -848,21 +555,21 @@ async function seedSecurityPoolX2AuctionScenario({ const secondaryAccount = requireQaAccount(accounts[1], 'Expected simulation QA account B2 for securitypoolx2-auction') const readClient = createReadClient() const writeClient = createWriteClient(primaryAccount) - const x2Pools = await loadAllSecurityPools(readClient) + const x2Pools = await getScenarioProtocol().loadAllSecurityPools(readClient) const parentPool = x2Pools.find(pool => pool.marketDetails.title === 'Will this resolve? (securitypoolx2 #1)') if (parentPool === undefined) throw new Error('Expected the first securitypoolx2 parent pool for auction scenario seeding') await reportBootstrapProgress(onProgress, 'Preparing fork-auction seed pool', 0.985) - await approveErc20(writeClient, profile.genesisRepTokenAddress, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_EXTRA_REP_DEPOSIT, 'approveRep') - await depositRepToVaultToSecurityPool(writeClient, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_EXTRA_REP_DEPOSIT) + await getScenarioProtocol().approveErc20(writeClient, profile.genesisRepTokenAddress, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_EXTRA_REP_DEPOSIT, 'approveRep') + await getScenarioProtocol().depositRepToVaultToSecurityPool(writeClient, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_EXTRA_REP_DEPOSIT) const secondaryWriteClient = createWriteClient(secondaryAccount) - await approveErc20(secondaryWriteClient, profile.genesisRepTokenAddress, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_UNMIGRATED_REP_DEPOSIT, 'approveRep') - await depositRepToVaultToSecurityPool(secondaryWriteClient, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_UNMIGRATED_REP_DEPOSIT) - await createCompleteSetInSecurityPool(createWriteClient(secondaryAccount), parentPool.securityPoolAddress, 20n * 10n ** 18n) + await getScenarioProtocol().approveErc20(secondaryWriteClient, profile.genesisRepTokenAddress, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_UNMIGRATED_REP_DEPOSIT, 'approveRep') + await getScenarioProtocol().depositRepToVaultToSecurityPool(secondaryWriteClient, parentPool.securityPoolAddress, SECURITY_POOL_X2_AUCTION_UNMIGRATED_REP_DEPOSIT) + await getScenarioProtocol().createCompleteSetInSecurityPool(createWriteClient(secondaryAccount), parentPool.securityPoolAddress, 20n * 10n ** 18n) - const universeSummary = await loadZoltarUniverseSummary(readClient, parentPool.universeId) + const universeSummary = await getScenarioProtocol().loadZoltarUniverseSummary(readClient, parentPool.universeId) if (universeSummary === undefined) throw new Error(`Expected a Zoltar universe summary for parent pool ${parentPool.securityPoolAddress}`) - const reportingDetailsBeforeFork = await loadReportingDetails(readClient, parentPool.securityPoolAddress, primaryAccount) + const reportingDetailsBeforeFork = await getScenarioProtocol().loadReportingDetails(readClient, parentPool.securityPoolAddress, primaryAccount) if (reportingDetailsBeforeFork.marketDetails.endTime >= reportingDetailsBeforeFork.currentTime) { await advanceSimulationTime(memoryClient, reportingDetailsBeforeFork.marketDetails.endTime - reportingDetailsBeforeFork.currentTime + DAY_IN_SECONDS) } @@ -876,21 +583,21 @@ async function seedSecurityPoolX2AuctionScenario({ readClient, }) await reportBootstrapProgress(onProgress, 'Triggering own-escalation fork', 0.988) - await reportOutcomeInSecurityPool(writeClient, parentPool.securityPoolAddress, 'yes', ownForkDepositAmount) - await reportOutcomeInSecurityPool(writeClient, parentPool.securityPoolAddress, 'no', ownForkDepositAmount) - await forkZoltarWithOwnEscalation(writeClient, parentPool.securityPoolAddress, parentPool.universeId) + await getScenarioProtocol().reportOutcomeInSecurityPool(writeClient, parentPool.securityPoolAddress, 'yes', ownForkDepositAmount) + await getScenarioProtocol().reportOutcomeInSecurityPool(writeClient, parentPool.securityPoolAddress, 'no', ownForkDepositAmount) + await getScenarioProtocol().forkZoltarWithOwnEscalation(writeClient, parentPool.securityPoolAddress, parentPool.universeId) await reportBootstrapProgress(onProgress, 'Creating and funding Yes child universe', 0.99) - await createChildUniverseFromSecurityPool(writeClient, parentPool.securityPoolAddress, parentPool.universeId, 'yes') - await migrateRepToZoltarFromSecurityPool(writeClient, parentPool.securityPoolAddress, parentPool.universeId, ['yes']) + await getScenarioProtocol().createChildUniverseFromSecurityPool(writeClient, parentPool.securityPoolAddress, parentPool.universeId, 'yes') + await getScenarioProtocol().migrateRepToZoltarFromSecurityPool(writeClient, parentPool.securityPoolAddress, parentPool.universeId, ['yes']) await advanceSimulationTime(memoryClient, FORK_MIGRATION_TIME_SECONDS + DAY_IN_SECONDS) const yesChildPool = await loadRequiredChildSecurityPool(readClient, parentPool.securityPoolAddress, 'yes') - const yesForkDetailsBeforeAuction = await loadForkAuctionDetails(readClient, yesChildPool.securityPoolAddress) + const yesForkDetailsBeforeAuction = await getScenarioProtocol().loadForkAuctionDetails(readClient, yesChildPool.securityPoolAddress) await reportBootstrapProgress(onProgress, 'Starting seeded truth auction', 0.992) - await startTruthAuctionForSecurityPool(writeClient, yesChildPool.securityPoolAddress, yesForkDetailsBeforeAuction.universeId) + await getScenarioProtocol().startTruthAuctionForSecurityPool(writeClient, yesChildPool.securityPoolAddress, yesForkDetailsBeforeAuction.universeId) - const yesForkDetails = await loadForkAuctionDetails(readClient, yesChildPool.securityPoolAddress) + const yesForkDetails = await getScenarioProtocol().loadForkAuctionDetails(readClient, yesChildPool.securityPoolAddress) if (yesForkDetails.truthAuctionAddress === undefined || yesForkDetails.truthAuctionAddress === '0x0000000000000000000000000000000000000000') { throw new Error('Expected a seeded truth auction address for the Yes child pool') } @@ -920,40 +627,18 @@ async function seedSecurityPoolX2AuctionScenario({ if (bidPrice === undefined) throw new Error(`Missing seeded truth auction bid price for bid ${index + 1}`) const bidTick = getTruthAuctionTickAtPrice(bidPrice) if (bidTick === undefined) throw new Error(`Unable to map seeded truth auction bid price to a tick for bid ${index + 1}`) - await submitTruthAuctionBid(createWriteClient(bidderAccount), yesChildPool.securityPoolAddress, yesForkDetails.universeId, yesForkDetails.truthAuctionAddress, bidTick, bidAmount) + await getScenarioProtocol().submitTruthAuctionBid(createWriteClient(bidderAccount), yesChildPool.securityPoolAddress, yesForkDetails.universeId, yesForkDetails.truthAuctionAddress, bidTick, bidAmount) } await reportBootstrapProgress(onProgress, 'Seeded securitypoolx2-auction scenario is ready', 0.995) } -async function applyScenario({ - accounts, - createReadClient, - createWriteClient, - memoryClient, - onProgress, - profile, - scenario, -}: { - accounts: readonly Address[] - createReadClient: () => ReadClient - createWriteClient: (accountAddress: Address) => WriteClient - memoryClient: TevmLikeClient - onProgress: BootstrapProgressHandler | undefined - profile: NetworkProfile - scenario: SimulationScenario -}) { +export async function applyStatoblastScenario({ accounts, createReadClient, createWriteClient, memoryClient, onProgress, profile, scenario }: BootstrapScenarioApplyParameters): Promise { const primaryAccount = requireQaAccount(accounts[0], 'Expected seeded simulation QA account A1') switch (scenario) { - case 'baseline': - await reportBootstrapProgress(onProgress, 'Using baseline simulation scenario', 0.84) - return - case 'deployed': - await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.92 }) - return case 'security-pool': - await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.78 }) + await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.78 }, getScenarioProtocol().getDeploymentSteps) await seedSecurityPoolScenario({ accounts, createReadClient, @@ -962,9 +647,9 @@ async function applyScenario({ onProgress, profile, }) - return + return true case 'securitypoolx2': - await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.7 }) + await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.7 }, getScenarioProtocol().getDeploymentSteps) await seedSecurityPoolX2Scenario({ accounts, createReadClient, @@ -973,9 +658,9 @@ async function applyScenario({ onProgress, profile, }) - return + return true case 'securitypoolx2-auction': - await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.7 }) + await deploySimulationAppContracts(createWriteClient(primaryAccount), memoryClient, onProgress, profile, { start: 0.32, end: 0.7 }, getScenarioProtocol().getDeploymentSteps) await seedSecurityPoolX2AuctionScenario({ accounts, createReadClient, @@ -984,59 +669,8 @@ async function applyScenario({ onProgress, profile, }) - return + return true default: - return assertNever(scenario) + return false } } - -export async function bootstrapSimulationChain({ - accounts, - createReadClient, - createWriteClient, - memoryClient, - onProgress, - primaryAccount, - profile, - scenario, -}: { - accounts: readonly Address[] - createReadClient: () => ReadClient - createWriteClient: (accountAddress: Address) => WriteClient - memoryClient: TevmLikeClient - onProgress: BootstrapProgressHandler | undefined - primaryAccount: Address - profile: NetworkProfile - scenario: SimulationScenario -}) { - setRuntimeNetworkProfile(profile) - await reportBootstrapProgress(onProgress, 'Initializing simulation engine', 0.01) - await withTimeout(memoryClient.tevmReady(), 20_000, 'Simulation engine initialization timed out. Firefox may be struggling with main-thread simulation startup.') - await reportBootstrapProgress(onProgress, 'Preparing simulation chain', 0.03) - await initializeSimulationClock(memoryClient) - await seedAccountBalances(memoryClient, accounts, onProgress) - const zoltarStep = getDeploymentSteps(profile).find(step => step.id === 'zoltar') - if (zoltarStep === undefined) throw new Error('Missing Zoltar deployment step for simulation bootstrap') - - await deploySimulationTokens({ - accounts, - createWriteClient, - memoryClient, - onProgress, - primaryAccount, - profile, - zoltarAddress: zoltarStep.address, - }) - await seedWrappedEthBalances(createWriteClient, accounts, profile.wethAddress, onProgress) - await applyScenario({ - accounts, - createReadClient, - createWriteClient, - memoryClient, - onProgress, - profile, - scenario, - }) - await reportBootstrapProgress(onProgress, 'Saving simulation snapshot', 0.99) - await reportBootstrapProgress(onProgress, 'Simulation scenario ready', 1) -} diff --git a/ui/statoblast/ts/simulation/tevmWorker.ts b/ui/statoblast/ts/simulation/tevmWorker.ts new file mode 100644 index 000000000..e495c0a14 --- /dev/null +++ b/ui/statoblast/ts/simulation/tevmWorker.ts @@ -0,0 +1,16 @@ +/// + +import { getDeploymentSteps } from '../protocol/deployment.js' +import { getZoltarAddress } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' +import type { SimulationEngineDependencies } from '@zoltar/ui-core-shared/simulation/tevmEngine.js' +import { applyStatoblastScenario } from './statoblastScenarios.js' + +const dependencies: SimulationEngineDependencies = { + applyScenario: applyStatoblastScenario, + getDeploymentSteps, + getZoltarAddress, +} + +globalThis.zoltarSimulationEngineDependencies = dependencies + +import '@zoltar/ui-core-shared/simulation/tevmWorker.js' diff --git a/ui/statoblast/ts/tests/app/activeEnvironment.test.ts b/ui/statoblast/ts/tests/app/activeEnvironment.test.ts new file mode 100644 index 000000000..47dec6b32 --- /dev/null +++ b/ui/statoblast/ts/tests/app/activeEnvironment.test.ts @@ -0,0 +1,14 @@ +import { expect, test } from 'bun:test' +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { statoblastActiveEnvironmentDependencies } from '../../app/activeEnvironment.js' + +test('Statoblast environment initialization always selects the Statoblast worker', async () => { + expect(statoblastActiveEnvironmentDependencies).toEqual({ appId: 'statoblast' }) + + for (const relativePath of ['../../app/App.tsx', '../../index.ts']) { + const source = await readFile(join(import.meta.dir, relativePath), 'utf8') + expect(source).toContain('initializeStatoblastActiveEnvironment') + expect(source).not.toContain('initializeActiveEnvironment(') + } +}) diff --git a/ui/ts/tests/app/appRouteEffects.integration.test.tsx b/ui/statoblast/ts/tests/app/appRouteEffects.integration.test.tsx similarity index 96% rename from ui/ts/tests/app/appRouteEffects.integration.test.tsx rename to ui/statoblast/ts/tests/app/appRouteEffects.integration.test.tsx index 08dd71dc9..43b8f21df 100644 --- a/ui/ts/tests/app/appRouteEffects.integration.test.tsx +++ b/ui/statoblast/ts/tests/app/appRouteEffects.integration.test.tsx @@ -1,14 +1,15 @@ /// import { describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { render } from 'preact' import { useState } from 'preact/hooks' import { act } from 'preact/test-utils' -import { useAppRouteEffects } from '../../app/hooks/useAppRouteEffects.js' -import { useUrlState } from '../../app/hooks/useUrlState.js' -import { installDomEnvironment } from '../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' +import { useAppRouteEffects } from '../../app/useAppRouteEffects.js' +import { useUrlState } from '@zoltar/ui-core-shared/app/hooks/useUrlState.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' type RouteEffectsProps = Parameters[0] @@ -17,7 +18,7 @@ function createDefaultProps(overrides: Partial = {}): RouteEf accountAddress: undefined, activeZoltarView: 'questions', activeEnvironmentNonce: 0, - augurStatoblastDeploymentMissing: false, + applicationDeploymentMissing: false, environmentReady: true, loadOracleReport: async () => undefined, loadSecurityPools: async () => undefined, @@ -92,6 +93,7 @@ function UrlStateHarness() { ) } +installTestRouting() describe('app route effects integration', () => { test('loads linked security pools once while browsing market questions', async () => { const dom = installDomEnvironment('http://localhost/#/zoltar') diff --git a/ui/ts/tests/app/appRouteEffects.test.ts b/ui/statoblast/ts/tests/app/appRouteEffects.test.ts similarity index 82% rename from ui/ts/tests/app/appRouteEffects.test.ts rename to ui/statoblast/ts/tests/app/appRouteEffects.test.ts index 5a4814a88..825703bd3 100644 --- a/ui/ts/tests/app/appRouteEffects.test.ts +++ b/ui/statoblast/ts/tests/app/appRouteEffects.test.ts @@ -1,14 +1,15 @@ /// import { describe, expect, test } from 'bun:test' -import { getSelectedVaultOwnerForRoutePoolChange, shouldLoadMarketSecurityPools, shouldLoadOpenOracleReportFromUrl, shouldRefreshSelectedPoolForRoute, shouldSyncSecurityPoolAddressToRouteForms } from '../../app/hooks/useAppRouteEffects.js' +import { getSelectedVaultOwnerForRoutePoolChange, shouldLoadOpenOracleReportFromUrl, shouldRefreshSelectedPoolForRoute, shouldSyncSecurityPoolAddressToRouteForms } from '../../app/useAppRouteEffects.js' +import { MAINNET_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { onchainStateDependencies } from '../../app/onchainStateDependencies.js' +import { getDeploymentSteps } from '../../protocol/deployment.js' describe('app route effects', () => { - test('loads linked security pools while browsing market questions', () => { - expect(shouldLoadMarketSecurityPools({ activeZoltarView: 'questions', environmentReady: true, route: 'zoltar', walletBootstrapComplete: true })).toBe(true) - expect(shouldLoadMarketSecurityPools({ activeZoltarView: 'create', environmentReady: true, route: 'zoltar', walletBootstrapComplete: true })).toBe(false) - expect(shouldLoadMarketSecurityPools({ activeZoltarView: 'questions', environmentReady: false, route: 'zoltar', walletBootstrapComplete: true })).toBe(false) - expect(shouldLoadMarketSecurityPools({ activeZoltarView: 'questions', environmentReady: true, route: 'security-pools', walletBootstrapComplete: true })).toBe(false) + test('injects the complete Statoblast deployment plan into shared onchain state', () => { + expect(onchainStateDependencies.getDeploymentSteps).toBe(getDeploymentSteps) + expect(onchainStateDependencies.getDeploymentSteps(MAINNET_NETWORK_PROFILE).some(step => step.id === 'securityPoolFactory')).toBe(true) }) test('loads the open oracle report from the URL only on the open-oracle route', () => { diff --git a/ui/ts/tests/features/markets/marketCreateQuestionSection.test.tsx b/ui/statoblast/ts/tests/features/markets/marketCreateQuestionSection.test.tsx similarity index 98% rename from ui/ts/tests/features/markets/marketCreateQuestionSection.test.tsx rename to ui/statoblast/ts/tests/features/markets/marketCreateQuestionSection.test.tsx index 43f5e821a..3e3296c40 100644 --- a/ui/ts/tests/features/markets/marketCreateQuestionSection.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/marketCreateQuestionSection.test.tsx @@ -1,17 +1,17 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' import { zeroAddress } from '@zoltar/shared/ethereum' import { MarketCreateQuestionSection } from '../../../features/markets/components/MarketCreateQuestionSection.js' -import { ChainTimestampContext } from '../../../lib/chainTimestamp.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' import { createMarketParameters } from '../../../features/markets/lib/marketCreation.js' -import type { MarketFormState } from '../../../types/app.js' -import type { MarketCreationResult, MarketDetails } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled } from '../../testUtils/transactionActionButton.js' +import type { MarketFormState } from '@zoltar/ui-zoltar/types/app.js' +import type { MarketCreationResult, MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' function createMarketForm(overrides: Partial = {}): MarketFormState { return { diff --git a/ui/ts/tests/features/markets/marketCreation.test.ts b/ui/statoblast/ts/tests/features/markets/marketCreation.test.ts similarity index 98% rename from ui/ts/tests/features/markets/marketCreation.test.ts rename to ui/statoblast/ts/tests/features/markets/marketCreation.test.ts index 34aab89ba..232cf93cd 100644 --- a/ui/ts/tests/features/markets/marketCreation.test.ts +++ b/ui/statoblast/ts/tests/features/markets/marketCreation.test.ts @@ -1,9 +1,10 @@ /// import { describe, expect, test } from 'bun:test' -import { createMarketParameters, createSecurityPoolParameters, hasDeployedStep, validateMarketForm } from '../../../features/markets/lib/marketCreation.js' +import { createMarketParameters, createSecurityPoolParameters, validateMarketForm } from '../../../features/markets/lib/marketCreation.js' +import { hasDeployedStep } from '@zoltar/ui-core-shared/lib/deploymentStatus.js' import { sortStringArrayByKeccak } from '@zoltar/shared/sortStringArrayByKeccak' -import type { MarketFormState, SecurityPoolFormState } from '../../../types/app.js' +import type { MarketFormState, SecurityPoolFormState } from '@zoltar/ui-zoltar/types/app.js' void describe('market creation helpers', () => { const noopDeploy = async () => '0x0000000000000000000000000000000000000000000000000000000000000000' as const diff --git a/ui/statoblast/ts/tests/features/markets/marketForm.test.ts b/ui/statoblast/ts/tests/features/markets/marketForm.test.ts new file mode 100644 index 000000000..c9da52a73 --- /dev/null +++ b/ui/statoblast/ts/tests/features/markets/marketForm.test.ts @@ -0,0 +1,17 @@ +/// + +import { describe, expect, test } from 'bun:test' +import { getDefaultForkAuctionFormState, getDefaultMarketFormState, getDefaultSecurityPoolFormState, getDefaultSecurityVaultFormState, getDefaultTradingFormState } from '../../../features/markets/lib/marketForm.js' + +describe('market form defaults', () => { + test('returns stable default form snapshots across all supported forms', () => { + expect(getDefaultMarketFormState().marketType).toBe('binary') + expect(getDefaultMarketFormState().categoricalOutcomes).toEqual(['Yes', 'No']) + expect(getDefaultSecurityPoolFormState().statoblastSecurityMultiplierBps).toBe('2') + expect(getDefaultSecurityPoolFormState().initialReportPriorityFeeGwei).toBe('10') + expect(getDefaultSecurityVaultFormState().depositAmount).toBe('0') + expect(getDefaultSecurityVaultFormState().stagedOperationTimeoutMinutes).toBe('5') + expect(getDefaultTradingFormState().selectedShareOutcome).toBe('yes') + expect(getDefaultForkAuctionFormState().repMigrationOutcomes).toBe('yes') + }) +}) diff --git a/ui/ts/tests/features/markets/marketOverviewWalletAsset.test.tsx b/ui/statoblast/ts/tests/features/markets/marketOverviewWalletAsset.test.tsx similarity index 79% rename from ui/ts/tests/features/markets/marketOverviewWalletAsset.test.tsx rename to ui/statoblast/ts/tests/features/markets/marketOverviewWalletAsset.test.tsx index 6b4498bf8..8663e31e9 100644 --- a/ui/ts/tests/features/markets/marketOverviewWalletAsset.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/marketOverviewWalletAsset.test.tsx @@ -3,12 +3,12 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { MarketOverviewSection } from '../../../features/markets/components/MarketOverviewSection.js' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { createInjectedBackend } from '../../../lib/chainBackend.js' -import type { ZoltarUniverseSummary } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { within } from '../../testUtils/queries.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createInjectedBackend } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' const TOKEN_ADDRESS = '0x00000000000000000000000000000000000000a1' diff --git a/ui/ts/tests/features/markets/outcomeSelectionList.test.tsx b/ui/statoblast/ts/tests/features/markets/outcomeSelectionList.test.tsx similarity index 85% rename from ui/ts/tests/features/markets/outcomeSelectionList.test.tsx rename to ui/statoblast/ts/tests/features/markets/outcomeSelectionList.test.tsx index a04ed11da..f022d49d8 100644 --- a/ui/ts/tests/features/markets/outcomeSelectionList.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/outcomeSelectionList.test.tsx @@ -1,11 +1,11 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' -import { OutcomeSelectionList } from '../../../features/markets/components/OutcomeSelectionList.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { OutcomeSelectionList } from '@zoltar/ui-core-shared/components/OutcomeSelectionList.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' describe('OutcomeSelectionList', () => { let restoreDomEnvironment: (() => void) | undefined diff --git a/ui/ts/tests/features/markets/questionComponent.test.tsx b/ui/statoblast/ts/tests/features/markets/questionComponent.test.tsx similarity index 92% rename from ui/ts/tests/features/markets/questionComponent.test.tsx rename to ui/statoblast/ts/tests/features/markets/questionComponent.test.tsx index bd29b9ef8..0e9095bb6 100644 --- a/ui/ts/tests/features/markets/questionComponent.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/questionComponent.test.tsx @@ -1,11 +1,11 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { Question } from '../../../features/markets/components/Question.js' -import { ChainTimestampContext } from '../../../lib/chainTimestamp.js' -import type { MarketDetails } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { Question } from '@zoltar/ui-core-shared/components/Question.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import type { MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' function createQuestion(overrides: Partial = {}): MarketDetails { return { diff --git a/ui/ts/tests/features/markets/questionHelpers.test.ts b/ui/statoblast/ts/tests/features/markets/questionHelpers.test.ts similarity index 94% rename from ui/ts/tests/features/markets/questionHelpers.test.ts rename to ui/statoblast/ts/tests/features/markets/questionHelpers.test.ts index c6a67affb..9efb0aa39 100644 --- a/ui/ts/tests/features/markets/questionHelpers.test.ts +++ b/ui/statoblast/ts/tests/features/markets/questionHelpers.test.ts @@ -1,8 +1,8 @@ /// import { describe, expect, test } from 'bun:test' -import type { MarketDetails } from '../../../types/contracts.js' -import { getQuestionSummaryFields, getQuestionTitle } from '../../../features/markets/components/Question.js' +import type { MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { getQuestionSummaryFields, getQuestionTitle } from '@zoltar/ui-core-shared/components/Question.js' const questionBase = { answerUnit: '', diff --git a/ui/ts/tests/features/markets/scalarCreatePreview.test.tsx b/ui/statoblast/ts/tests/features/markets/scalarCreatePreview.test.tsx similarity index 91% rename from ui/ts/tests/features/markets/scalarCreatePreview.test.tsx rename to ui/statoblast/ts/tests/features/markets/scalarCreatePreview.test.tsx index 1cf4c676b..7ee129f66 100644 --- a/ui/ts/tests/features/markets/scalarCreatePreview.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/scalarCreatePreview.test.tsx @@ -1,12 +1,12 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' import { useState } from 'preact/hooks' import { ScalarCreatePreview } from '../../../features/markets/components/ScalarCreatePreview.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' const SCALE = 10n ** 18n diff --git a/ui/ts/tests/features/deployment/scalarDeploymentSection.test.tsx b/ui/statoblast/ts/tests/features/markets/scalarDeploymentSection.test.tsx similarity index 92% rename from ui/ts/tests/features/deployment/scalarDeploymentSection.test.tsx rename to ui/statoblast/ts/tests/features/markets/scalarDeploymentSection.test.tsx index b24df2b07..25f1ce223 100644 --- a/ui/ts/tests/features/deployment/scalarDeploymentSection.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/scalarDeploymentSection.test.tsx @@ -1,16 +1,17 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' import { render } from 'preact' import { zeroAddress } from '@zoltar/shared/ethereum' import { ScalarDeploymentSection } from '../../../features/markets/components/ScalarDeploymentSection.js' -import { getScalarOutcomeIndex } from '../../../features/markets/lib/scalarOutcome.js' -import type { MarketDetails, ZoltarChildUniverseSummary } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' +import { getScalarOutcomeIndex } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' +import type { MarketDetails, ZoltarChildUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' function createQuestionDetails(overrides: Partial = {}): MarketDetails { return { @@ -44,6 +45,7 @@ function createChildUniverse(overrides: Partial = {} } } +installTestRouting() describe('ScalarDeploymentSection', () => { let restoreDomEnvironment: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/markets/scalarOutcome.test.ts b/ui/statoblast/ts/tests/features/markets/scalarOutcome.test.ts similarity index 99% rename from ui/ts/tests/features/markets/scalarOutcome.test.ts rename to ui/statoblast/ts/tests/features/markets/scalarOutcome.test.ts index 38f8ac3f7..c8caece17 100644 --- a/ui/ts/tests/features/markets/scalarOutcome.test.ts +++ b/ui/statoblast/ts/tests/features/markets/scalarOutcome.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from 'bun:test' import { SCALAR_PARITY_ENCODING_FIXTURES, SCALAR_PARITY_LABEL_FIXTURES, combineScalarParityOutcomeIndex, describeScalarParityOutcomeIndex, formatScalarParityOutcomeName, getScalarParityQuestion } from '@zoltar/shared/testing/scalarOutcomeParityFixtures' -import { formatScalarOutcomeIndexLabel, formatScalarOutcomeLabel, getScalarDisplayValue, getScalarOutcomeIndex, getScalarOutcomeIndexDescriptor, getScalarSliderProgress, getScalarTickIndexForDisplayValue, isValidScalarOutcomeIndex, parseScalarFormInputs } from '../../../features/markets/lib/scalarOutcome.js' +import { formatScalarOutcomeIndexLabel, formatScalarOutcomeLabel, getScalarDisplayValue, getScalarOutcomeIndex, getScalarOutcomeIndexDescriptor, getScalarSliderProgress, getScalarTickIndexForDisplayValue, isValidScalarOutcomeIndex, parseScalarFormInputs } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' const scalarQuestion = { answerUnit: 'km', diff --git a/ui/ts/tests/features/markets/scalarOutcomePicker.test.tsx b/ui/statoblast/ts/tests/features/markets/scalarOutcomePicker.test.tsx similarity index 95% rename from ui/ts/tests/features/markets/scalarOutcomePicker.test.tsx rename to ui/statoblast/ts/tests/features/markets/scalarOutcomePicker.test.tsx index b9afc72df..040400d27 100644 --- a/ui/ts/tests/features/markets/scalarOutcomePicker.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/scalarOutcomePicker.test.tsx @@ -1,12 +1,12 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { useState } from 'preact/hooks' import { act } from 'preact/test-utils' -import { ScalarOutcomePicker } from '../../../features/markets/components/ScalarOutcomePicker.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { ScalarOutcomePicker } from '@zoltar/ui-core-shared/components/ScalarOutcomePicker.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' function ScalarOutcomePickerHarness() { const [selectedTick, setSelectedTick] = useState('2') diff --git a/ui/ts/tests/features/markets/trading.test.ts b/ui/statoblast/ts/tests/features/markets/trading.test.ts similarity index 99% rename from ui/ts/tests/features/markets/trading.test.ts rename to ui/statoblast/ts/tests/features/markets/trading.test.ts index 32403c367..f59236d52 100644 --- a/ui/ts/tests/features/markets/trading.test.ts +++ b/ui/statoblast/ts/tests/features/markets/trading.test.ts @@ -30,8 +30,8 @@ import { hasRepBackedPoolWithNoActiveCapacityOwnership, isTradingSystemDeployed, } from '../../../features/markets/lib/trading.js' -import { getScalarOutcomeIndex } from '../../../features/markets/lib/scalarOutcome.js' -import type { DeploymentStatus, ZoltarUniverseSummary } from '../../../types/contracts.js' +import { getScalarOutcomeIndex } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' +import type { DeploymentStatus, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' const TOKEN_PRECISION = 10n ** 18n diff --git a/ui/ts/tests/features/markets/tradingSection.test.tsx b/ui/statoblast/ts/tests/features/markets/tradingSection.test.tsx similarity index 97% rename from ui/ts/tests/features/markets/tradingSection.test.tsx rename to ui/statoblast/ts/tests/features/markets/tradingSection.test.tsx index 9e9232d1f..02977f786 100644 --- a/ui/ts/tests/features/markets/tradingSection.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/tradingSection.test.tsx @@ -1,21 +1,21 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { useEffect, useState } from 'preact/hooks' import { act } from 'preact/test-utils' import { zeroAddress, zeroHash } from '@zoltar/shared/ethereum' import { TradingSection } from '../../../features/markets/components/TradingSection.js' -import { GlobalTransactionPresentationProvider } from '../../../components/GlobalTransactionPresentationContext.js' -import { ChainTimestampContext } from '../../../lib/chainTimestamp.js' +import { GlobalTransactionPresentationProvider } from '@zoltar/ui-core-shared/components/GlobalTransactionPresentationContext.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' import { deriveHasForkActivity } from '../../../features/truth-auctions/lib/forkAuction.js' import { NEED_MATCHING_COMPLETE_SET_SHARES_MESSAGE, NO_MINT_CAPACITY_NO_ACTIVE_CAPACITY_OWNERSHIP_MESSAGE, UNDEFINED_COMPLETE_SET_EXCHANGE_RATE_MESSAGE } from '../../../features/markets/lib/trading.js' -import type { AccountState, TradingFormState } from '../../../types/app.js' -import type { ListedSecurityPool, MarketDetails, TradingActionResult, TradingDetails, TradingShareBalances, ZoltarUniverseSummary } from '../../../types/contracts.js' -import type { TradingSectionProps } from '../../../features/types.js' -import type { GlobalTransactionPresentation } from '../../../types/components.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import type { AccountState, TradingFormState } from '@zoltar/ui-zoltar/types/app.js' +import type { ListedSecurityPool, MarketDetails, TradingActionResult, TradingDetails, TradingShareBalances, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import type { TradingSectionProps } from '@zoltar/ui-zoltar/features/types.js' +import type { GlobalTransactionPresentation } from '@zoltar/ui-core-shared/types/components.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' function createMarketDetails(): MarketDetails { return { diff --git a/ui/ts/tests/features/markets/useMarketCreation.test.tsx b/ui/statoblast/ts/tests/features/markets/useMarketCreation.test.tsx similarity index 95% rename from ui/ts/tests/features/markets/useMarketCreation.test.tsx rename to ui/statoblast/ts/tests/features/markets/useMarketCreation.test.tsx index 268fb3468..e2c28117a 100644 --- a/ui/ts/tests/features/markets/useMarketCreation.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/useMarketCreation.test.tsx @@ -4,13 +4,13 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h, render } from 'preact' import { act } from 'preact/test-utils' import { getAddress, type Address, type Hash } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { waitFor } from '../../testUtils/queries' -import type { DeploymentStatus, MarketCreationResult } from '../../../types/contracts.js' -import type { MarketFormState } from '../../../types/app.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import type { DeploymentStatus, MarketCreationResult } from '@zoltar/ui-core-shared/types/contracts.js' +import type { MarketFormState } from '@zoltar/ui-zoltar/types/app.js' import type { UseMarketCreationDependencies } from '../../../features/markets/hooks/useMarketCreation.js' type UseMarketCreation = typeof import('../../../features/markets/hooks/useMarketCreation.js')['useMarketCreation'] @@ -79,7 +79,7 @@ describe('useMarketCreation', () => { const dependencies: UseMarketCreationDependencies = { createMarket: createMarketTransaction, } - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions, setZoltarForkQuestionId, @@ -160,7 +160,7 @@ describe('useMarketCreation', () => { const dependencies: UseMarketCreationDependencies = { createMarket: createMarketTransaction, } - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions, setZoltarForkQuestionId, @@ -239,7 +239,7 @@ describe('useMarketCreation', () => { const dependencies: UseMarketCreationDependencies = { createMarket: createMarketTransaction, } - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions, setZoltarForkQuestionId, @@ -308,7 +308,7 @@ describe('useMarketCreation', () => { }) test('preserves an anonymous question draft when a wallet connects', async () => { - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions: async () => undefined, setZoltarForkQuestionId: () => undefined, @@ -358,7 +358,7 @@ describe('useMarketCreation', () => { }) test('keeps the anonymous draft when the connected account already has a draft', async () => { - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions: async () => undefined, setZoltarForkQuestionId: () => undefined, @@ -402,7 +402,7 @@ describe('useMarketCreation', () => { }) test('keeps the anonymous draft when copying it to connected storage fails', async () => { - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions: async () => undefined, setZoltarForkQuestionId: () => undefined, @@ -471,7 +471,7 @@ describe('useMarketCreation', () => { marketType: 'scalar' as const, questionId: '0x0b', })) - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions: async () => undefined, setZoltarForkQuestionId: () => undefined, @@ -589,7 +589,7 @@ describe('useMarketCreation', () => { test('keeps another universe draft visible when an in-flight question succeeds in the original universe', async () => { const pendingCreate = createDeferred() - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions: async () => undefined, setZoltarForkQuestionId: () => undefined, @@ -669,7 +669,7 @@ describe('useMarketCreation', () => { }) test('ignores corrupt or unavailable question draft storage without blocking form use', async () => { - mock.module('../../../features/universes/hooks/useZoltarOperations.js', () => ({ + mock.module('@zoltar/ui-zoltar/features/universes/hooks/useZoltarOperations.js', () => ({ useZoltarOperations: mock(() => ({ loadZoltarQuestions: async () => undefined, setZoltarForkQuestionId: () => undefined, diff --git a/ui/ts/tests/features/markets/useTradingOperations.test.tsx b/ui/statoblast/ts/tests/features/markets/useTradingOperations.test.tsx similarity index 97% rename from ui/ts/tests/features/markets/useTradingOperations.test.tsx rename to ui/statoblast/ts/tests/features/markets/useTradingOperations.test.tsx index 943dd639c..99248a966 100644 --- a/ui/ts/tests/features/markets/useTradingOperations.test.tsx +++ b/ui/statoblast/ts/tests/features/markets/useTradingOperations.test.tsx @@ -5,15 +5,15 @@ import { h } from 'preact' import { useState } from 'preact/hooks' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, zeroHash, type Address } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { createInitialTransactionTrayState, markTransactionCanceled, markTransactionFinished, markTransactionRequested } from '../../../lib/transactionTray.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createInitialTransactionTrayState, markTransactionCanceled, markTransactionFinished, markTransactionRequested } from '@zoltar/ui-core-shared/lib/transactionTray.js' import { useTradingOperations, type UseTradingOperationsDependencies } from '../../../features/markets/hooks/useTradingOperations.js' -import type { TransactionIntent } from '../../../features/types.js' -import type { DeploymentStatus, TradingDetails, ZoltarUniverseSummary } from '../../../types/contracts.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { waitFor } from '../../testUtils/queries' +import type { TransactionIntent } from '@zoltar/ui-zoltar/features/types.js' +import type { DeploymentStatus, TradingDetails, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' type UseTradingOperations = typeof useTradingOperations type UseTradingOperationsState = ReturnType diff --git a/ui/ts/tests/features/security-pools/collateralizationCircle.test.tsx b/ui/statoblast/ts/tests/features/security-pools/collateralizationCircle.test.tsx similarity index 95% rename from ui/ts/tests/features/security-pools/collateralizationCircle.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/collateralizationCircle.test.tsx index 5096ac4eb..ebadd80c8 100644 --- a/ui/ts/tests/features/security-pools/collateralizationCircle.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/collateralizationCircle.test.tsx @@ -1,15 +1,15 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { within } from '../../testUtils/queries' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { pathToFileURL } from 'node:url' import { CollateralizationCircle } from '../../../features/security-pools/components/CollateralizationCircle.js' -import { getChromiumPath, withChromiumTestLock } from '../../../../build/chromiumPath.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { getChromiumPath, withChromiumTestLock } from '../../../../../coreShared/build/chromiumPath.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' type GaugeFitResult = { clientWidth: number @@ -120,8 +120,8 @@ describe('CollateralizationCircle', () => { if (chromiumPath === undefined) throw new Error('Chromium is required for the browser fit test') const temporaryDirectory = mkdtempSync(join(tmpdir(), 'zoltar#collateralization-circle-')) try { - const tokensSource = readFileSync('ui/css/tokens.css', 'utf8') - const cssSource = readFileSync('ui/css/index.css', 'utf8') + const tokensSource = readFileSync('ui/coreShared/css/tokens.css', 'utf8') + const cssSource = readFileSync('ui/coreShared/css/index.css', 'utf8') const htmlPath = join(temporaryDirectory, 'gauge-fit.html') writeFileSync( htmlPath, diff --git a/ui/ts/tests/features/security-pools/collateralizationMetricField.test.tsx b/ui/statoblast/ts/tests/features/security-pools/collateralizationMetricField.test.tsx similarity index 95% rename from ui/ts/tests/features/security-pools/collateralizationMetricField.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/collateralizationMetricField.test.tsx index 121ff5c59..1b86b0d85 100644 --- a/ui/ts/tests/features/security-pools/collateralizationMetricField.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/collateralizationMetricField.test.tsx @@ -1,11 +1,11 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { within } from '../../testUtils/queries' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { readFileSync } from 'node:fs' import { CollateralizationMetricField } from '../../../features/security-pools/components/CollateralizationMetricField.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' describe('CollateralizationMetricField', () => { let restoreDomEnvironment: (() => void) | undefined @@ -101,7 +101,7 @@ describe('CollateralizationMetricField', () => { }) test('keeps success and danger color rules specific enough for every collateralization container', () => { - const cssSource = readFileSync('ui/css/index.css', 'utf8') + const cssSource = readFileSync('ui/coreShared/css/index.css', 'utf8') for (const selector of ['.workflow-metric-grid strong.metric-value-success', '.workflow-question-grid strong.metric-value-success', '.workflow-vault-grid strong.metric-value-success', '.entity-metric strong.metric-value-success', '.selected-pool-context-grid strong.metric-value-success']) { expect(cssSource).toContain(selector) diff --git a/ui/ts/tests/features/security-pools/lifecycleStageBanner.test.tsx b/ui/statoblast/ts/tests/features/security-pools/lifecycleStageBanner.test.tsx similarity index 86% rename from ui/ts/tests/features/security-pools/lifecycleStageBanner.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/lifecycleStageBanner.test.tsx index 223dc2b53..300099756 100644 --- a/ui/ts/tests/features/security-pools/lifecycleStageBanner.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/lifecycleStageBanner.test.tsx @@ -2,10 +2,10 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { readFileSync } from 'node:fs' -import { within } from '../../testUtils/queries' -import { LifecycleStageBanner } from '../../../features/security-pools/components/LifecycleStageBanner.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { LifecycleStageBanner } from '@zoltar/ui-core-shared/components/LifecycleStageBanner.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' describe('LifecycleStageBanner', () => { let restoreDomEnvironment: (() => void) | undefined @@ -35,7 +35,7 @@ describe('LifecycleStageBanner', () => { expect(document.body.querySelector('.lifecycle-stage-banner.flat.success')).not.toBeNull() expect(document.body.querySelector('.warning-surface.lifecycle-stage-banner.flat')).not.toBeNull() - const cssSource = readFileSync('ui/css/index.css', 'utf8') + const cssSource = readFileSync('ui/coreShared/css/index.css', 'utf8') const baseFlatRuleIndex = cssSource.indexOf('.lifecycle-stage-banner.flat {') const warningFlatRuleIndex = cssSource.indexOf('.warning-surface.lifecycle-stage-banner.flat {') const criticalFlatRuleIndex = cssSource.indexOf('.lifecycle-stage-banner.flat.critical {') diff --git a/ui/ts/tests/features/security-pools/liquidationModal.test.tsx b/ui/statoblast/ts/tests/features/security-pools/liquidationModal.test.tsx similarity index 99% rename from ui/ts/tests/features/security-pools/liquidationModal.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/liquidationModal.test.tsx index ca1d7cc4b..8074570ec 100644 --- a/ui/ts/tests/features/security-pools/liquidationModal.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/liquidationModal.test.tsx @@ -1,20 +1,20 @@ /// import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { render } from 'preact' import { useState } from 'preact/hooks' import { act } from 'preact/test-utils' import { getAddress, zeroAddress } from '@zoltar/shared/ethereum' import { LiquidationModal } from '../../../features/security-pools/components/LiquidationModal.js' import { isVaultHealthyAtFactor, simulateLiquidation } from '../../../features/security-pools/lib/liquidation.js' -import { ChainTimestampContext } from '../../../lib/chainTimestamp.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' import { deriveHasForkActivity } from '../../../features/truth-auctions/lib/forkAuction.js' import { evaluateSecurityPoolState } from '../../../features/security-pools/lib/securityPoolState.js' -import type { LiquidationApprovalDetails, ListedSecurityPool, MarketDetails, OracleManagerDetails, SecurityPoolOverviewActionResult, SecurityPoolVaultSummary } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, getTransactionButtonState } from '../../testUtils/transactionActionButton.js' +import type { LiquidationApprovalDetails, ListedSecurityPool, MarketDetails, OracleManagerDetails, SecurityPoolOverviewActionResult, SecurityPoolVaultSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, getTransactionButtonState } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' const ATTO_ETH_PER_ETH = 10n ** 18n @@ -780,8 +780,8 @@ describe('LiquidationModal', () => { }) cleanupRenderedComponent = renderedComponent.cleanup - expectTransactionButtonDisabled(document.body, 'Queue liquidation', 'Need 7 more ETH in this wallet to queue liquidation.') - expect(within(document.body).getByText('Need 7 more ETH in this wallet to queue liquidation.')).not.toBeNull() + expectTransactionButtonDisabled(document.body, 'Queue liquidation', 'Need 7\u00a0more\u00a0ETH in this wallet to queue liquidation.') + expect(within(document.body).getByText('Need 7\u00a0more\u00a0ETH in this wallet to queue liquidation.')).not.toBeNull() }) test('allows queued liquidation when the entered amount exceeds the executable cap because execution will clamp it', async () => { diff --git a/ui/ts/tests/features/security-pools/liquidationStatus.test.ts b/ui/statoblast/ts/tests/features/security-pools/liquidationStatus.test.ts similarity index 100% rename from ui/ts/tests/features/security-pools/liquidationStatus.test.ts rename to ui/statoblast/ts/tests/features/security-pools/liquidationStatus.test.ts diff --git a/ui/ts/tests/features/security-pools/securityPoolCreation.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolCreation.test.ts similarity index 79% rename from ui/ts/tests/features/security-pools/securityPoolCreation.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolCreation.test.ts index 53c01f3a5..42d571664 100644 --- a/ui/ts/tests/features/security-pools/securityPoolCreation.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolCreation.test.ts @@ -2,20 +2,20 @@ import { beforeEach, describe, expect, setDefaultTimeout, test } from 'bun:test' import { encodeAbiParameters, encodeEventTopics, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { createSecurityPool } from '../../../protocol/index.js' -import { createWalletWriteClient } from '../../../lib/clients.js' -import type { WriteClient as UiWriteClient } from '../../../types/contracts.js' -import type { InjectedEthereum } from '../../../injectedEthereum.js' -import { DAY, TEST_ADDRESSES } from '../../../../../solidity/ts/testSupport/simulator/utils/constants' -import { addressString } from '../../../../../solidity/ts/testSupport/simulator/utils/bigint' -import { AnvilWindowEthereum } from '../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' -import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' -import { createWriteClient, type WriteClient as SolidityWriteClient } from '../../../../../solidity/ts/testSupport/simulator/utils/clients' -import { ensureInfraDeployed, getInfraContractAddresses, getSecurityPoolAddresses } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' -import { ensureZoltarDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' -import { createQuestion, getQuestionId } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltarQuestionData' -import { ensureProxyDeployerDeployed, setupTestAccounts } from '../../../../../solidity/ts/testSupport/simulator/utils/utilities' -import { statoblast_factories_SecurityPoolFactory_SecurityPoolFactory } from '../../../../../solidity/ts/types/contractArtifact' +import { createSecurityPool } from '../../../protocol/securityPools.js' +import { createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import type { WriteClient as UiWriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import type { InjectedEthereum } from '@zoltar/ui-core-shared/injectedEthereum.js' +import { DAY, TEST_ADDRESSES } from '../../../../../../solidity/ts/testSupport/simulator/utils/constants' +import { addressString } from '../../../../../../solidity/ts/testSupport/simulator/utils/bigint' +import { AnvilWindowEthereum } from '../../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' +import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' +import { createWriteClient, type WriteClient as SolidityWriteClient } from '../../../../../../solidity/ts/testSupport/simulator/utils/clients' +import { ensureInfraDeployed, getInfraContractAddresses, getSecurityPoolAddresses } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' +import { ensureZoltarDeployed } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' +import { createQuestion, getQuestionId } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltarQuestionData' +import { ensureProxyDeployerDeployed, setupTestAccounts } from '../../../../../../solidity/ts/testSupport/simulator/utils/utilities' +import { statoblast_factories_SecurityPoolFactory_SecurityPoolFactory } from '@zoltar/ui-core-shared/contractArtifact.js' setDefaultTimeout(TEST_TIMEOUT_MS) diff --git a/ui/ts/tests/features/security-pools/securityPoolCreationGuards.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolCreationGuards.test.ts similarity index 98% rename from ui/ts/tests/features/security-pools/securityPoolCreationGuards.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolCreationGuards.test.ts index 620a6834b..3dc31ab93 100644 --- a/ui/ts/tests/features/security-pools/securityPoolCreationGuards.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolCreationGuards.test.ts @@ -4,7 +4,7 @@ import { describe, expect, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { MAX_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS } from '@zoltar/shared/oracleInitialReport' import { getInitialReportPriorityFeeValidationMessage, getSecurityPoolCreateDisabledReason, getStatoblastSecurityMultiplierValidationMessage } from '../../../features/security-pools/lib/securityPoolCreationGuards.js' -import type { MarketDetails } from '../../../types/contracts.js' +import type { MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' function createMarketDetails(overrides: Partial = {}): MarketDetails { return { diff --git a/ui/ts/tests/features/security-pools/securityPoolCreationState.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolCreationState.test.ts similarity index 100% rename from ui/ts/tests/features/security-pools/securityPoolCreationState.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolCreationState.test.ts diff --git a/ui/ts/tests/features/security-pools/securityPoolLabels.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolLabels.test.ts similarity index 100% rename from ui/ts/tests/features/security-pools/securityPoolLabels.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolLabels.test.ts diff --git a/ui/ts/tests/features/security-pools/securityPoolLink.test.tsx b/ui/statoblast/ts/tests/features/security-pools/securityPoolLink.test.tsx similarity index 89% rename from ui/ts/tests/features/security-pools/securityPoolLink.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/securityPoolLink.test.tsx index 7a09eca16..c2b602dd9 100644 --- a/ui/ts/tests/features/security-pools/securityPoolLink.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolLink.test.tsx @@ -1,14 +1,16 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' import { getAddress } from '@zoltar/shared/ethereum' import { SecurityPoolLink } from '../../../features/security-pools/components/SecurityPoolLink.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { getSecurityPoolLinkHref } from '../../../features/security-pools/lib/securityPoolNavigation.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('SecurityPoolLink', () => { let restoreDomEnvironment: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/security-pools/securityPoolNavigation.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolNavigation.test.ts similarity index 94% rename from ui/ts/tests/features/security-pools/securityPoolNavigation.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolNavigation.test.ts index 4a5e2b210..b8a471f53 100644 --- a/ui/ts/tests/features/security-pools/securityPoolNavigation.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolNavigation.test.ts @@ -3,8 +3,10 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { getAddress } from '@zoltar/shared/ethereum' import { getSecurityPoolLinkHref, getUseQuestionForPoolHref } from '../../../features/security-pools/lib/securityPoolNavigation.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('securityPoolNavigation', () => { let restoreDomEnvironment: (() => void) | undefined diff --git a/ui/ts/tests/features/security-pools/securityPoolSection.test.tsx b/ui/statoblast/ts/tests/features/security-pools/securityPoolSection.test.tsx similarity index 97% rename from ui/ts/tests/features/security-pools/securityPoolSection.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/securityPoolSection.test.tsx index ca68a5a19..25c69d54b 100644 --- a/ui/ts/tests/features/security-pools/securityPoolSection.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolSection.test.tsx @@ -1,18 +1,19 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { h, render } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, zeroHash } from '@zoltar/shared/ethereum' import { SecurityPoolSection } from '../../../features/security-pools/components/SecurityPoolSection.js' import { formatOpenInterestFeePerYearPercent, ORIGIN_POOL_INITIAL_RETENTION_RATE } from '../../../features/security-pools/lib/retentionRate.js' -import type { AccountState } from '../../../types/app.js' -import type { MarketDetails } from '../../../types/contracts.js' -import type { SecurityPoolSectionProps } from '../../../features/types.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' +import type { AccountState } from '@zoltar/ui-zoltar/types/app.js' +import type { MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import type { SecurityPoolSectionProps } from '@zoltar/ui-zoltar/features/types.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' function createAccountState(overrides: Partial = {}): AccountState { return { @@ -80,6 +81,7 @@ function createProps(overrides: Partial = {}): Securit } } +installTestRouting() describe('SecurityPoolSection', () => { let restoreDomEnvironment: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/security-pools/securityPoolState.axes.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolState.axes.test.ts similarity index 98% rename from ui/ts/tests/features/security-pools/securityPoolState.axes.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolState.axes.test.ts index 9dfd4a7ee..b54ffab5d 100644 --- a/ui/ts/tests/features/security-pools/securityPoolState.axes.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolState.axes.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from 'bun:test' import { getSecurityPoolLifecycleLabel } from '../../../features/security-pools/lib/securityPoolLabels.js' import { deriveSecurityPoolForkStage, deriveSecurityPoolLifecycleState, deriveSecurityPoolReportingStage, isSecurityPoolEnded } from '../../../features/security-pools/lib/securityPoolState.js' -import type { ActiveReportingDetails } from '../../../types/contracts.js' +import type { ActiveReportingDetails } from '@zoltar/ui-core-shared/types/contracts.js' function createActiveReportingDetails(overrides: Partial = {}): ActiveReportingDetails { return { diff --git a/ui/ts/tests/features/security-pools/securityPoolState.engine.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolState.engine.test.ts similarity index 100% rename from ui/ts/tests/features/security-pools/securityPoolState.engine.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolState.engine.test.ts diff --git a/ui/ts/tests/features/security-pools/securityPoolState.matrix.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolState.matrix.test.ts similarity index 100% rename from ui/ts/tests/features/security-pools/securityPoolState.matrix.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolState.matrix.test.ts diff --git a/ui/ts/tests/features/security-pools/securityPoolWorkflow.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolWorkflow.test.ts similarity index 99% rename from ui/ts/tests/features/security-pools/securityPoolWorkflow.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolWorkflow.test.ts index e503caaee..a9ec95918 100644 --- a/ui/ts/tests/features/security-pools/securityPoolWorkflow.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolWorkflow.test.ts @@ -8,8 +8,6 @@ import { getCurrentSelectedPoolReportingDetails, getCurrentSelectedPoolForkStage, getCurrentPoolOracleManagerDetails, - getOracleLastPriceDisplay, - getOraclePriceValidityPresentation, getSelectedPoolCardTitle, getSelectedPoolForkWorkflowView, getForkWorkflowStageSelection, @@ -25,6 +23,7 @@ import { resolveSelectedPoolView, shouldShowSelectedPoolWorkflowDetails, } from '../../../features/security-pools/lib/securityPoolWorkflow.js' +import { getOracleLastPriceDisplay, getOraclePriceValidityPresentation } from '@zoltar/ui-zoltar/features/open-oracle/lib/openOracle.js' import { ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS } from '../../../features/security-pools/lib/securityVault.js' void describe('selected pool workflow lookup state', () => { diff --git a/ui/ts/tests/features/security-pools/securityPoolsOverviewSection.test.tsx b/ui/statoblast/ts/tests/features/security-pools/securityPoolsOverviewSection.test.tsx similarity index 98% rename from ui/ts/tests/features/security-pools/securityPoolsOverviewSection.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/securityPoolsOverviewSection.test.tsx index 59765fddd..7303dc8b7 100644 --- a/ui/ts/tests/features/security-pools/securityPoolsOverviewSection.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolsOverviewSection.test.tsx @@ -1,18 +1,19 @@ /// import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { render } from 'preact' import { SecurityPoolsOverviewSection } from '../../../features/security-pools/components/SecurityPoolsOverviewSection.js' import { deriveHasForkActivity } from '../../../features/truth-auctions/lib/forkAuction.js' -import { getWalletScopedAccountAddress } from '../../../lib/network.js' -import type { AccountState } from '../../../types/app.js' -import type { ListedSecurityPool, MarketDetails, SecurityPoolBrowsePage, SecurityPoolPage } from '../../../types/contracts.js' -import type { SecurityPoolsOverviewSectionProps } from '../../../features/types.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { getWalletScopedAccountAddress } from '@zoltar/ui-core-shared/lib/network.js' +import type { AccountState } from '@zoltar/ui-zoltar/types/app.js' +import type { ListedSecurityPool, MarketDetails, SecurityPoolBrowsePage, SecurityPoolPage } from '@zoltar/ui-core-shared/types/contracts.js' +import type { SecurityPoolsOverviewSectionProps } from '@zoltar/ui-zoltar/features/types.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { act } from 'preact/test-utils' import { zeroAddress } from '@zoltar/shared/ethereum' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' function createAccountState(overrides: Partial = {}): AccountState { return { @@ -139,6 +140,7 @@ function createProps(overrides: SecurityPoolsOverviewSectionTestOverrides = {}): } } +installTestRouting() describe('SecurityPoolsOverviewSection', () => { let restoreDomEnvironment: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/security-pools/securityPoolsSection.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityPoolsSection.test.ts similarity index 97% rename from ui/ts/tests/features/security-pools/securityPoolsSection.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityPoolsSection.test.ts index 8752bb871..fcc531557 100644 --- a/ui/ts/tests/features/security-pools/securityPoolsSection.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityPoolsSection.test.ts @@ -1,18 +1,20 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { h } from 'preact' import { render } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, zeroHash, type Address } from '@zoltar/shared/ethereum' import { SecurityPoolsSection, shouldRefreshSelectedPoolDataOnViewOpen } from '../../../features/security-pools/components/SecurityPoolsSection.js' import { deriveHasForkActivity } from '../../../features/truth-auctions/lib/forkAuction.js' -import type { AccountState } from '../../../types/app.js' -import type { ListedSecurityPool, MarketDetails, SecurityPoolBrowsePage, SecurityPoolPage } from '../../../types/contracts.js' -import type { ForkAuctionRouteContentProps, ReportingRouteContentProps, SecurityPoolRouteContentProps, SecurityPoolsOverviewRouteContentProps, SecurityPoolsSectionProps, SecurityPoolWorkflowRouteContentProps, SecurityVaultRouteContentProps, TradingRouteContentProps } from '../../../features/types.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import type { AccountState } from '@zoltar/ui-zoltar/types/app.js' +import type { ListedSecurityPool, MarketDetails, SecurityPoolBrowsePage, SecurityPoolPage } from '@zoltar/ui-core-shared/types/contracts.js' +import type { ForkAuctionRouteContentProps, ReportingRouteContentProps, SecurityPoolRouteContentProps, SecurityPoolsOverviewRouteContentProps, SecurityPoolsSectionProps, SecurityPoolWorkflowRouteContentProps, SecurityVaultRouteContentProps, TradingRouteContentProps } from '@zoltar/ui-zoltar/features/types.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() function createAccountState(overrides: Partial = {}): AccountState { return { diff --git a/ui/ts/tests/features/security-pools/securityVault.integration.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityVault.integration.test.ts similarity index 80% rename from ui/ts/tests/features/security-pools/securityVault.integration.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityVault.integration.test.ts index 7d97cfee6..069119559 100644 --- a/ui/ts/tests/features/security-pools/securityVault.integration.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityVault.integration.test.ts @@ -2,19 +2,22 @@ import { beforeEach, describe, expect, setDefaultTimeout, test } from 'bun:test' import { zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { approveErc20, depositRepToVaultToSecurityPool, loadErc20Allowance, loadErc20Balance, loadSecurityVaultDetails } from '../../../protocol/index.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import type { InjectedEthereum } from '../../../injectedEthereum.js' -import { DAY, TEST_ADDRESSES } from '../../../../../solidity/ts/testSupport/simulator/utils/constants' -import { addressString } from '../../../../../solidity/ts/testSupport/simulator/utils/bigint' -import { ensureProxyDeployerDeployed, setupTestAccounts } from '../../../../../solidity/ts/testSupport/simulator/utils/utilities' -import { AnvilWindowEthereum } from '../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' -import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' -import { createWriteClient, type WriteClient } from '../../../../../solidity/ts/testSupport/simulator/utils/clients' -import { deployOriginSecurityPool, ensureInfraDeployed, getSecurityPoolAddresses } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' -import { ensureZoltarDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' -import { createQuestion, getQuestionId } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltarQuestionData' -import { getSecurityVault, getVaultCount, getVaults, backingUnitsToAttoRep } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/securityPool' +import { approveErc20 } from '../../../protocol/tokenActions.js' +import { depositRepToVaultToSecurityPool } from '../../../protocol/securityVault.js' +import { loadSecurityVaultDetails } from '../../../protocol/securityPools.js' +import { loadErc20Allowance, loadErc20Balance } from '@zoltar/ui-zoltar/protocol/deployment.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import type { InjectedEthereum } from '@zoltar/ui-core-shared/injectedEthereum.js' +import { DAY, TEST_ADDRESSES } from '../../../../../../solidity/ts/testSupport/simulator/utils/constants' +import { addressString } from '../../../../../../solidity/ts/testSupport/simulator/utils/bigint' +import { ensureProxyDeployerDeployed, setupTestAccounts } from '../../../../../../solidity/ts/testSupport/simulator/utils/utilities' +import { AnvilWindowEthereum } from '../../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' +import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' +import { createWriteClient, type WriteClient } from '../../../../../../solidity/ts/testSupport/simulator/utils/clients' +import { deployOriginSecurityPool, ensureInfraDeployed, getSecurityPoolAddresses } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' +import { ensureZoltarDeployed } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' +import { createQuestion, getQuestionId } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltarQuestionData' +import { getSecurityVault, getVaultCount, getVaults, backingUnitsToAttoRep } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/securityPool' setDefaultTimeout(TEST_TIMEOUT_MS) diff --git a/ui/ts/tests/features/security-pools/securityVault.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityVault.test.ts similarity index 98% rename from ui/ts/tests/features/security-pools/securityVault.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityVault.test.ts index 7b8d1a588..c91dca1ec 100644 --- a/ui/ts/tests/features/security-pools/securityVault.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/securityVault.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from 'bun:test' import { getAddress, zeroAddress } from '@zoltar/shared/ethereum' -import { formatCurrencyInputBalance } from '../../../lib/formatters.js' -import { parseOptionalRepAmountInput, parseRepAmountInput } from '../../../features/markets/lib/marketForm.js' +import { formatCurrencyInputBalance } from '@zoltar/ui-core-shared/lib/formatters.js' +import { parseOptionalRepAmountInput, parseRepAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' import { doesLoadedSecurityVaultMatchSelection, doesSecurityVaultExistOnchain, @@ -18,8 +18,8 @@ import { MIN_SECURITY_VAULT_REP_DEPOSIT_ATTO_REP, ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS, } from '../../../features/security-pools/lib/securityVault.js' -import { createConnectedReadClient } from '../../../lib/clients.js' -import { loadSecurityVaultDetails } from '../../../protocol/index.js' +import { createConnectedReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { loadSecurityVaultDetails } from '../../../protocol/securityPools.js' void describe('security vault helpers', () => { void test('defaults to the connected wallet vault when no explicit vault is selected', () => { diff --git a/ui/ts/tests/features/security-pools/securityVaultGuards.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityVaultGuards.test.ts similarity index 100% rename from ui/ts/tests/features/security-pools/securityVaultGuards.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityVaultGuards.test.ts diff --git a/ui/ts/tests/features/security-pools/securityVaultHandlers.test.ts b/ui/statoblast/ts/tests/features/security-pools/securityVaultHandlers.test.ts similarity index 100% rename from ui/ts/tests/features/security-pools/securityVaultHandlers.test.ts rename to ui/statoblast/ts/tests/features/security-pools/securityVaultHandlers.test.ts diff --git a/ui/ts/tests/features/security-pools/securityVaultSection.test.tsx b/ui/statoblast/ts/tests/features/security-pools/securityVaultSection.test.tsx similarity index 97% rename from ui/ts/tests/features/security-pools/securityVaultSection.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/securityVaultSection.test.tsx index ae952f32a..6b17e615c 100644 --- a/ui/ts/tests/features/security-pools/securityVaultSection.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/securityVaultSection.test.tsx @@ -1,17 +1,17 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { zeroAddress } from '@zoltar/shared/ethereum' import { SecurityVaultSection, SelectedVaultSummarySection } from '../../../features/security-pools/components/SecurityVaultSection.js' import { evaluateSecurityPoolState } from '../../../features/security-pools/lib/securityPoolState.js' -import type { AccountState } from '../../../types/app.js' -import type { SecurityVaultDetails } from '../../../types/contracts.js' -import type { SecurityVaultSectionProps } from '../../../features/types.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled, getTransactionButtonState } from '../../testUtils/transactionActionButton.js' -import { ChainTimestampContext } from '../../../lib/chainTimestamp.js' +import type { AccountState } from '@zoltar/ui-zoltar/types/app.js' +import type { SecurityVaultDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import type { SecurityVaultSectionProps } from '@zoltar/ui-zoltar/features/types.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled, getTransactionButtonState } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' function createAccountState(overrides: Partial = {}): AccountState { return { @@ -692,7 +692,7 @@ describe('SecurityVaultSection', () => { const documentQueries = within(document.body) fireEvent.click(documentQueries.getByRole('button', { name: 'Deposit REP' })) - expectTransactionButtonDisabled(documentQueries.getByRole('dialog', { name: 'Deposit REP' }), 'Deposit REP', 'New vaults require at least 30 REP in the first deposit.') + expectTransactionButtonDisabled(documentQueries.getByRole('dialog', { name: 'Deposit REP' }), 'Deposit REP', 'New vaults require at least 30\u00a0REP in the first deposit.') }) test('allows REP withdrawal staging when the oracle price is stale but fresh-report funding is available', async () => { @@ -770,7 +770,7 @@ describe('SecurityVaultSection', () => { ) cleanupRenderedComponent = renderedComponent.cleanup - expectTransactionButtonDisabled(document.body, 'Withdraw REP', 'Need 1.2 more ETH in this wallet to queue this REP withdrawal.') + expectTransactionButtonDisabled(document.body, 'Withdraw REP', 'Need 1.2\u00a0more\u00a0ETH in this wallet to queue this REP withdrawal.') }) test('does not require fresh-report funding immediately before oracle-price expiry', async () => { diff --git a/ui/ts/tests/features/security-pools/testSupport/securityPoolsOverviewDependencies.ts b/ui/statoblast/ts/tests/features/security-pools/testSupport/securityPoolsOverviewDependencies.ts similarity index 97% rename from ui/ts/tests/features/security-pools/testSupport/securityPoolsOverviewDependencies.ts rename to ui/statoblast/ts/tests/features/security-pools/testSupport/securityPoolsOverviewDependencies.ts index f85cea506..06bf015f8 100644 --- a/ui/ts/tests/features/security-pools/testSupport/securityPoolsOverviewDependencies.ts +++ b/ui/statoblast/ts/tests/features/security-pools/testSupport/securityPoolsOverviewDependencies.ts @@ -1,7 +1,7 @@ import { mock } from 'bun:test' import { zeroAddress, zeroHash } from '@zoltar/shared/ethereum' import { createSecurityPoolPageFromLoadedPools, type UseSecurityPoolsOverviewDependencies } from '../../../../features/security-pools/hooks/useSecurityPoolsOverview.js' -import type { OracleManagerDetails } from '../../../../types/contracts.js' +import type { OracleManagerDetails } from '@zoltar/ui-core-shared/types/contracts.js' export type TestSecurityPoolsOverviewWriteClient = { kind: 'write-client' } diff --git a/ui/ts/tests/features/security-pools/useSecurityPoolCreation.test.tsx b/ui/statoblast/ts/tests/features/security-pools/useSecurityPoolCreation.test.tsx similarity index 98% rename from ui/ts/tests/features/security-pools/useSecurityPoolCreation.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/useSecurityPoolCreation.test.tsx index 7da98178f..36cece00e 100644 --- a/ui/ts/tests/features/security-pools/useSecurityPoolCreation.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/useSecurityPoolCreation.test.tsx @@ -2,13 +2,14 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { act } from 'preact/test-utils' -import { waitFor } from '../../testUtils/queries' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { zeroAddress, type Address, type Hash } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import type { DeploymentStatus, MarketDetails, SecurityPoolCreationResult } from '../../../types/contracts.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import type { DeploymentStatus, MarketDetails, SecurityPoolCreationResult } from '@zoltar/ui-core-shared/types/contracts.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' type UseSecurityPoolCreation = typeof import('../../../features/security-pools/hooks/useSecurityPoolCreation.js')['useSecurityPoolCreation'] type UseSecurityPoolCreationState = ReturnType @@ -80,7 +81,7 @@ function setupContractMocks({ loadMarketDetails, createSecurityPool, originSecur originSecurityPoolExists: originSecurityPoolExists ?? mock(async () => false), })) - mock.module('../../../lib/clients.js', () => ({ + mock.module('@zoltar/ui-core-shared/lib/clients.js', () => ({ createConnectedReadClient: mock(() => ({ kind: 'read-client' })), createWalletWriteClient: mock((walletAddress: Address, options: { onTransactionSubmitted: (hash: Hash) => void }) => ({ walletAddress, @@ -104,6 +105,7 @@ function createHarness(useSecurityPoolCreation: UseSecurityPoolCreation, props: } } +installTestRouting() describe('useSecurityPoolCreation', () => { let cleanupDom: (() => void) | undefined let restoreActiveEnvironment: (() => void) | undefined diff --git a/ui/ts/tests/features/security-pools/useSecurityPoolsOverview.test.ts b/ui/statoblast/ts/tests/features/security-pools/useSecurityPoolsOverview.test.ts similarity index 96% rename from ui/ts/tests/features/security-pools/useSecurityPoolsOverview.test.ts rename to ui/statoblast/ts/tests/features/security-pools/useSecurityPoolsOverview.test.ts index fcf998259..2d053a71d 100644 --- a/ui/ts/tests/features/security-pools/useSecurityPoolsOverview.test.ts +++ b/ui/statoblast/ts/tests/features/security-pools/useSecurityPoolsOverview.test.ts @@ -6,12 +6,12 @@ import { render } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, zeroHash, type Address } from '@zoltar/shared/ethereum' import { createSecurityPoolPageFromLoadedPools, shouldFallbackToAllSecurityPoolsPage, useSecurityPoolsOverview, type UseSecurityPoolsOverviewDependencies } from '../../../features/security-pools/hooks/useSecurityPoolsOverview.js' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import type { ListedSecurityPool, MarketDetails } from '../../../types/contracts.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { waitFor } from '../../testUtils/queries' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import type { ListedSecurityPool, MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { createSecurityPoolsOverviewDependencies, type TestSecurityPoolsOverviewWriteClient } from './testSupport/securityPoolsOverviewDependencies.js' type UseSecurityPoolsOverviewState = ReturnType diff --git a/ui/ts/tests/features/security-pools/useSecurityPoolsOverviewQueueLiquidation.test.tsx b/ui/statoblast/ts/tests/features/security-pools/useSecurityPoolsOverviewQueueLiquidation.test.tsx similarity index 97% rename from ui/ts/tests/features/security-pools/useSecurityPoolsOverviewQueueLiquidation.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/useSecurityPoolsOverviewQueueLiquidation.test.tsx index 2b4520bd8..f2d862a73 100644 --- a/ui/ts/tests/features/security-pools/useSecurityPoolsOverviewQueueLiquidation.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/useSecurityPoolsOverviewQueueLiquidation.test.tsx @@ -4,15 +4,15 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h, render, type ComponentChildren } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { waitFor } from '../../testUtils/queries' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { createSecurityPoolsOverviewDependencies, type TestSecurityPoolsOverviewWriteClient } from './testSupport/securityPoolsOverviewDependencies.js' import { useSecurityPoolsOverview, type UseSecurityPoolsOverviewDependencies } from '../../../features/security-pools/hooks/useSecurityPoolsOverview.js' -import type { GlobalTransactionPresentation } from '../../../features/types.js' -import type { LiquidationApprovalDetails } from '../../../types/contracts.js' +import type { GlobalTransactionPresentation } from '@zoltar/ui-zoltar/features/types.js' +import type { LiquidationApprovalDetails } from '@zoltar/ui-core-shared/types/contracts.js' type UseSecurityPoolsOverviewState = ReturnType type HarnessOptions = { diff --git a/ui/ts/tests/features/security-pools/useSecurityVaultOperations.test.tsx b/ui/statoblast/ts/tests/features/security-pools/useSecurityVaultOperations.test.tsx similarity index 98% rename from ui/ts/tests/features/security-pools/useSecurityVaultOperations.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/useSecurityVaultOperations.test.tsx index 19584bfb7..891281dba 100644 --- a/ui/ts/tests/features/security-pools/useSecurityVaultOperations.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/useSecurityVaultOperations.test.tsx @@ -4,12 +4,12 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { waitFor } from '../../testUtils/queries' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import type { OracleManagerDetails, SecurityVaultDetails } from '../../../types/contracts.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import type { OracleManagerDetails, SecurityVaultDetails } from '@zoltar/ui-core-shared/types/contracts.js' import { useSecurityVaultOperations, type UseSecurityVaultOperationsDependencies } from '../../../features/security-pools/hooks/useSecurityVaultOperations.js' type UseSecurityVaultOperationsState = ReturnType diff --git a/ui/ts/tests/features/security-pools/workflow/README.md b/ui/statoblast/ts/tests/features/security-pools/workflow/README.md similarity index 61% rename from ui/ts/tests/features/security-pools/workflow/README.md rename to ui/statoblast/ts/tests/features/security-pools/workflow/README.md index d2feb9f33..42c165d63 100644 --- a/ui/ts/tests/features/security-pools/workflow/README.md +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/README.md @@ -9,8 +9,8 @@ bun run test:security-pool-workflow Run one workflow slice directly: ```bash -bun test --timeout 300000 ui/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx -bun test --timeout 300000 ui/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx +bun test --timeout 300000 ui/statoblast/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx +bun test --timeout 300000 ui/statoblast/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx ``` Use `useSecurityPoolWorkflowSectionTestDom().renderWorkflow(...)` for direct component renders. Use `renderLoadedPool(...)` when a test only needs a selected pool shell. diff --git a/ui/ts/tests/features/security-pools/workflow/builders.ts b/ui/statoblast/ts/tests/features/security-pools/workflow/builders.ts similarity index 98% rename from ui/ts/tests/features/security-pools/workflow/builders.ts rename to ui/statoblast/ts/tests/features/security-pools/workflow/builders.ts index b1ffc5dd4..01bf84f53 100644 --- a/ui/ts/tests/features/security-pools/workflow/builders.ts +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/builders.ts @@ -1,8 +1,8 @@ import { zeroAddress, type Address } from '@zoltar/shared/ethereum' import { deriveHasForkActivity } from '../../../../features/truth-auctions/lib/forkAuction.js' -import type { AccountState } from '../../../../types/app.js' -import type { ForkAuctionDetails, ListedSecurityPool, MarketDetails, OracleManagerDetails, SecurityPoolVaultSummary, SecurityVaultDetails } from '../../../../types/contracts.js' -import type { ForkAuctionRouteContentProps, ReportingRouteContentProps, SecurityPoolWorkflowRouteContentProps, SecurityVaultRouteContentProps, TradingRouteContentProps } from '../../../../features/types.js' +import type { AccountState } from '@zoltar/ui-zoltar/types/app.js' +import type { ForkAuctionDetails, ListedSecurityPool, MarketDetails, OracleManagerDetails, SecurityPoolVaultSummary, SecurityVaultDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import type { ForkAuctionRouteContentProps, ReportingRouteContentProps, SecurityPoolWorkflowRouteContentProps, SecurityVaultRouteContentProps, TradingRouteContentProps } from '@zoltar/ui-zoltar/features/types.js' export function createAccountState(overrides: Partial = {}): AccountState { return { diff --git a/ui/ts/tests/features/security-pools/workflow/fixture.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/fixture.tsx similarity index 91% rename from ui/ts/tests/features/security-pools/workflow/fixture.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/fixture.tsx index f50a64ffd..e363c28cf 100644 --- a/ui/ts/tests/features/security-pools/workflow/fixture.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/fixture.tsx @@ -1,15 +1,15 @@ /// -import { fireEvent, waitFor, within } from '../../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { render } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress } from '@zoltar/shared/ethereum' import { pickFixtureProperties } from '@zoltar/shared/testing/pickFixtureProperties' import { SecurityPoolWorkflowSection } from '../../../../features/security-pools/components/SecurityPoolWorkflowSection.js' -import { ChainTimestampContext } from '../../../../lib/chainTimestamp.js' -import { getReportingLockedUntilMessage } from '../../../../features/reporting/lib/reporting.js' -import { renderIntoDocument } from '../../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../../testUtils/transactionActionButton.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { getReportingLockedUntilMessage } from '@zoltar/ui-zoltar/features/reporting/lib/reporting.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' import { createAccountState, createForkAuctionDetails, diff --git a/ui/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx similarity index 99% rename from ui/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx index 3d93a686c..2abb6475d 100644 --- a/ui/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/forkWorkflowState.test.tsx @@ -1,7 +1,9 @@ import { describe, expect, test } from 'bun:test' import { useState } from 'preact/hooks' import { createForkWorkflowStateFixture, useSecurityPoolWorkflowSectionTestDom } from './fixture' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('SecurityPoolWorkflowSection: fork workflow state', () => { const testDom = useSecurityPoolWorkflowSectionTestDom() diff --git a/ui/ts/tests/features/security-pools/workflow/refreshAutoload.test.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/refreshAutoload.test.tsx similarity index 98% rename from ui/ts/tests/features/security-pools/workflow/refreshAutoload.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/refreshAutoload.test.tsx index 7429f5416..34c2e01f9 100644 --- a/ui/ts/tests/features/security-pools/workflow/refreshAutoload.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/refreshAutoload.test.tsx @@ -1,7 +1,9 @@ import { describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { createRefreshAutoloadFixture, useSecurityPoolWorkflowSectionTestDom } from './fixture' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('SecurityPoolWorkflowSection: refresh and autoload', () => { const testDom = useSecurityPoolWorkflowSectionTestDom() const { setCleanup } = testDom diff --git a/ui/ts/tests/features/security-pools/workflow/reportingAndOracle.test.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/reportingAndOracle.test.tsx similarity index 99% rename from ui/ts/tests/features/security-pools/workflow/reportingAndOracle.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/reportingAndOracle.test.tsx index ef308fffd..e04a105d4 100644 --- a/ui/ts/tests/features/security-pools/workflow/reportingAndOracle.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/reportingAndOracle.test.tsx @@ -1,6 +1,8 @@ import { describe, expect, test } from 'bun:test' import { createReportingAndOracleFixture, useSecurityPoolWorkflowSectionTestDom } from './fixture' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('SecurityPoolWorkflowSection: reporting and oracle', () => { const testDom = useSecurityPoolWorkflowSectionTestDom() const { setCleanup } = testDom @@ -669,7 +671,7 @@ describe('SecurityPoolWorkflowSection: reporting and oracle', () => { ) setCleanup(renderedComponent.cleanup) - expectTransactionButtonDisabled(document.body, 'Request new price', 'Need 7 more ETH in this wallet to request a new price.') + expectTransactionButtonDisabled(document.body, 'Request new price', 'Need 7\u00a0more\u00a0ETH in this wallet to request a new price.') }) test('disables Request New Price while the current oracle price remains valid', async () => { diff --git a/ui/ts/tests/features/security-pools/workflow/selectedPoolState.test.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/selectedPoolState.test.tsx similarity index 99% rename from ui/ts/tests/features/security-pools/workflow/selectedPoolState.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/selectedPoolState.test.tsx index 533adb6cf..4e607c9ea 100644 --- a/ui/ts/tests/features/security-pools/workflow/selectedPoolState.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/selectedPoolState.test.tsx @@ -1,7 +1,9 @@ import { describe, expect, test } from 'bun:test' import { createSelectedPoolStateFixture, useSecurityPoolWorkflowSectionTestDom } from './fixture' -import { getTransactionButtonState } from '../../../testUtils/transactionActionButton.js' +import { getTransactionButtonState } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('SecurityPoolWorkflowSection: selected pool state', () => { const testDom = useSecurityPoolWorkflowSectionTestDom() const { renderLoadedPool, renderWorkflow, setCleanup } = testDom diff --git a/ui/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx similarity index 99% rename from ui/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx index a139052ed..57284b918 100644 --- a/ui/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/stagedOperations.test.tsx @@ -1,7 +1,9 @@ import { describe, expect, test } from 'bun:test' -import { ChainTimestampContext } from '../../../../lib/chainTimestamp.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' import { createStagedOperationsFixture, useSecurityPoolWorkflowSectionTestDom } from './fixture' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('SecurityPoolWorkflowSection: staged operations', () => { const testDom = useSecurityPoolWorkflowSectionTestDom() diff --git a/ui/ts/tests/features/security-pools/workflow/testDom.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/testDom.tsx similarity index 84% rename from ui/ts/tests/features/security-pools/workflow/testDom.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/testDom.tsx index 728af5401..0316b4ef4 100644 --- a/ui/ts/tests/features/security-pools/workflow/testDom.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/testDom.tsx @@ -3,9 +3,9 @@ import { afterEach, beforeEach } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { SecurityPoolWorkflowSection } from '../../../../features/security-pools/components/SecurityPoolWorkflowSection.js' -import type { SecurityPoolWorkflowRouteContentProps } from '../../../../features/types.js' -import { installDomEnvironment } from '../../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../../testUtils/renderIntoDocument.js' +import type { SecurityPoolWorkflowRouteContentProps } from '@zoltar/ui-zoltar/features/types.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { createSecurityPoolWorkflowProps, createSelectedPool } from './builders.js' export function useSecurityPoolWorkflowSectionTestDom() { diff --git a/ui/ts/tests/features/security-pools/workflow/vaultControls.test.tsx b/ui/statoblast/ts/tests/features/security-pools/workflow/vaultControls.test.tsx similarity index 97% rename from ui/ts/tests/features/security-pools/workflow/vaultControls.test.tsx rename to ui/statoblast/ts/tests/features/security-pools/workflow/vaultControls.test.tsx index 8c65efc86..f693ff005 100644 --- a/ui/ts/tests/features/security-pools/workflow/vaultControls.test.tsx +++ b/ui/statoblast/ts/tests/features/security-pools/workflow/vaultControls.test.tsx @@ -1,6 +1,8 @@ import { describe, expect, test } from 'bun:test' import { createVaultControlsFixture, useSecurityPoolWorkflowSectionTestDom } from './fixture' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +installTestRouting() describe('SecurityPoolWorkflowSection: vault controls', () => { const testDom = useSecurityPoolWorkflowSectionTestDom() const { setCleanup } = testDom @@ -191,7 +193,7 @@ describe('SecurityPoolWorkflowSection: vault controls', () => { const withdrawDialog = documentQueries.getByRole('dialog', { name: 'Withdraw REP', }) - expectTransactionButtonDisabled(withdrawDialog as HTMLElement, 'Withdraw REP', 'Reduce the withdrawal to 5 000 REP or less.') + expectTransactionButtonDisabled(withdrawDialog as HTMLElement, 'Withdraw REP', 'Reduce the withdrawal to 5 000\u00a0REP or less.') }) test('blocks withdraw REP in the workflow modal when the wallet lacks the buffered oracle bounty ETH', async () => { @@ -253,6 +255,6 @@ describe('SecurityPoolWorkflowSection: vault controls', () => { const withdrawDialog = documentQueries.getByRole('dialog', { name: 'Withdraw REP', }) - expectTransactionButtonDisabled(withdrawDialog as HTMLElement, 'Withdraw REP', 'Need 7 more ETH in this wallet to queue this REP withdrawal.') + expectTransactionButtonDisabled(withdrawDialog as HTMLElement, 'Withdraw REP', 'Need 7\u00a0more\u00a0ETH in this wallet to queue this REP withdrawal.') }) }) diff --git a/ui/ts/tests/features/transactionPresentations.test.ts b/ui/statoblast/ts/tests/features/transactionPresentations.test.ts similarity index 98% rename from ui/ts/tests/features/transactionPresentations.test.ts rename to ui/statoblast/ts/tests/features/transactionPresentations.test.ts index 0a6e9672d..32d5ad097 100644 --- a/ui/ts/tests/features/transactionPresentations.test.ts +++ b/ui/statoblast/ts/tests/features/transactionPresentations.test.ts @@ -19,8 +19,8 @@ import { createTradingSuccessPresentation, createTradingTransactionIntent, } from '../../features/transactionPresentations.js' -import type { ForkAuctionActionResult } from '../../types/contracts.js' -import { createInitialTransactionTrayState, markTransactionFailed, markTransactionPrepared, markTransactionRequested, markTransactionSubmitted } from '../../lib/transactionTray.js' +import type { ForkAuctionActionResult } from '@zoltar/ui-core-shared/types/contracts.js' +import { createInitialTransactionTrayState, markTransactionFailed, markTransactionPrepared, markTransactionRequested, markTransactionSubmitted } from '@zoltar/ui-core-shared/lib/transactionTray.js' const transactionHash = '0x1234000000000000000000000000000000000000000000000000000000000000' diff --git a/ui/ts/tests/features/truth-auctions/forkAuction.test.ts b/ui/statoblast/ts/tests/features/truth-auctions/forkAuction.test.ts similarity index 99% rename from ui/ts/tests/features/truth-auctions/forkAuction.test.ts rename to ui/statoblast/ts/tests/features/truth-auctions/forkAuction.test.ts index a536bda1f..27ad32fc6 100644 --- a/ui/ts/tests/features/truth-auctions/forkAuction.test.ts +++ b/ui/statoblast/ts/tests/features/truth-auctions/forkAuction.test.ts @@ -24,8 +24,8 @@ import { TRUTH_AUCTION_PRICE_PRECISION, } from '../../../features/truth-auctions/lib/truthAuctionBook.js' import { getTruthAuctionSettlementActionAvailabilityMessage, getTruthAuctionSettlementBidKey, getTruthAuctionSettlementBidRows, getTruthAuctionSettlementSelectionEstimate, getTruthAuctionSettlementSelectionState } from '../../../features/truth-auctions/lib/truthAuctionSettlement.js' -import type { TruthAuctionBidView, TruthAuctionMetrics, TruthAuctionTickSummary } from '../../../types/contracts.js' -import { formatCurrencyInputBalance } from '../../../lib/formatters.js' +import type { TruthAuctionBidView, TruthAuctionMetrics, TruthAuctionTickSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { formatCurrencyInputBalance } from '@zoltar/ui-core-shared/lib/formatters.js' const walletAddress: Address = '0x0000000000000000000000000000000000000001' const otherWalletAddress: Address = '0x0000000000000000000000000000000000000002' diff --git a/ui/ts/tests/features/truth-auctions/forkAuctionChildPoolRecovery.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/forkAuctionChildPoolRecovery.test.tsx similarity index 96% rename from ui/ts/tests/features/truth-auctions/forkAuctionChildPoolRecovery.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/forkAuctionChildPoolRecovery.test.tsx index ff26b7545..1c81af248 100644 --- a/ui/ts/tests/features/truth-auctions/forkAuctionChildPoolRecovery.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/forkAuctionChildPoolRecovery.test.tsx @@ -1,19 +1,20 @@ /// import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { h, render } from 'preact' import { act } from 'preact/test-utils' import { getAddress, type Address, zeroAddress } from '@zoltar/shared/ethereum' -import type { ForkAuctionSectionProps } from '../../../features/types.js' -import type { AccountState, ForkAuctionFormState } from '../../../types/app.js' -import type { ForkAuctionDetails, ListedSecurityPool, MarketDetails } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' +import type { ForkAuctionSectionProps } from '@zoltar/ui-zoltar/features/types.js' +import type { AccountState, ForkAuctionFormState } from '@zoltar/ui-zoltar/types/app.js' +import type { ForkAuctionDetails, ListedSecurityPool, MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' const actualContracts = await import('../../../protocol/index.js') -const actualClients = await import('../../../lib/clients.js') +const actualClients = await import('@zoltar/ui-core-shared/lib/clients.js') const PARENT_POOL_ADDRESS: Address = '0x00000000000000000000000000000000000000f0' const YES_CHILD_POOL_ADDRESS: Address = '0x00000000000000000000000000000000000000f1' @@ -42,7 +43,7 @@ mock.module('../../../protocol/index.js', () => ({ }), })) -mock.module('../../../lib/clients.js', () => ({ +mock.module('@zoltar/ui-core-shared/lib/clients.js', () => ({ ...actualClients, createConnectedReadClient: mock(() => ({ readContract: mock(async () => { @@ -274,6 +275,7 @@ function createProps(overrides: Partial = {}): ForkAuct } } +installTestRouting() describe('ForkAuctionSection child pool recovery', () => { let cleanupDom: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/truth-auctions/forkAuctionSection.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/forkAuctionSection.test.tsx similarity index 98% rename from ui/ts/tests/features/truth-auctions/forkAuctionSection.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/forkAuctionSection.test.tsx index da02fa267..fe5658c2f 100644 --- a/ui/ts/tests/features/truth-auctions/forkAuctionSection.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/forkAuctionSection.test.tsx @@ -1,15 +1,16 @@ /// import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { h } from 'preact' import { type Address, getAddress, zeroAddress } from '@zoltar/shared/ethereum' import { ForkAuctionSection } from '../../../features/truth-auctions/components/ForkAuctionSection.js' -import type { ForkAuctionSectionProps } from '../../../features/types.js' -import type { AccountState, ForkAuctionFormState, ReportingFormState } from '../../../types/app.js' -import type { EscalationDeposit, ForkAuctionDetails, ListedSecurityPool, MarketDetails, ReadClient, ReportingDetails } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import type { ForkAuctionSectionProps } from '@zoltar/ui-zoltar/features/types.js' +import type { AccountState, ForkAuctionFormState, ReportingFormState } from '@zoltar/ui-zoltar/types/app.js' +import type { EscalationDeposit, ForkAuctionDetails, ListedSecurityPool, MarketDetails, ReadClient, ReportingDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' const PARENT_POOL_ADDRESS: Address = '0x00000000000000000000000000000000000000f0' @@ -243,6 +244,7 @@ function createProps(overrides: Partial = {}): ForkAuct } } +installTestRouting() describe('ForkAuctionSection', () => { let cleanupDom: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/truth-auctions/forkAuctionSettlementSummary.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/forkAuctionSettlementSummary.test.tsx similarity index 95% rename from ui/ts/tests/features/truth-auctions/forkAuctionSettlementSummary.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/forkAuctionSettlementSummary.test.tsx index 10f17da40..7b0f99a17 100644 --- a/ui/ts/tests/features/truth-auctions/forkAuctionSettlementSummary.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/forkAuctionSettlementSummary.test.tsx @@ -1,20 +1,21 @@ /// import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { h } from 'preact' import { act } from 'preact/test-utils' import { getAddress, type Address, zeroAddress } from '@zoltar/shared/ethereum' import { getTruthAuctionBidDisposition, TRUTH_AUCTION_PRICE_PRECISION } from '../../../features/truth-auctions/lib/truthAuctionBook.js' import { getTruthAuctionSettlementBidKey, getTruthAuctionSettlementSelectionState, type TruthAuctionSettlementBidRow } from '../../../features/truth-auctions/lib/truthAuctionSettlement.js' -import type { AccountState, ForkAuctionFormState } from '../../../types/app.js' -import type { ForkAuctionSectionProps } from '../../../features/types.js' -import type { ForkAuctionDetails, ListedSecurityPool, MarketDetails, TruthAuctionBidView, TruthAuctionMetrics } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import type { AccountState, ForkAuctionFormState } from '@zoltar/ui-zoltar/types/app.js' +import type { ForkAuctionSectionProps } from '@zoltar/ui-zoltar/features/types.js' +import type { ForkAuctionDetails, ListedSecurityPool, MarketDetails, TruthAuctionBidView, TruthAuctionMetrics } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' const actualContracts = await import('../../../protocol/index.js') -const actualClients = await import('../../../lib/clients.js') +const actualClients = await import('@zoltar/ui-core-shared/lib/clients.js') const actualTruthAuctionBookHook = await import('../../../features/truth-auctions/hooks/useTruthAuctionBookData.js') const actualTruthAuctionSettlementHook = await import('../../../features/truth-auctions/hooks/useTruthAuctionSettlementActionState.js') @@ -39,7 +40,7 @@ mock.module('../../../protocol/index.js', () => ({ loadForkAuctionDetails: mock(async () => mockedForkAuctionDetails), })) -mock.module('../../../lib/clients.js', () => ({ +mock.module('@zoltar/ui-core-shared/lib/clients.js', () => ({ ...actualClients, createConnectedReadClient: mock(() => ({ readContract: mock(async () => { @@ -299,6 +300,7 @@ function createProps(overrides: Partial = {}): ForkAuct } } +installTestRouting() describe('ForkAuctionSection settlement summary', () => { let cleanupDom: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/truth-auctions/truthAuctionBidsSection.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/truthAuctionBidsSection.test.tsx similarity index 97% rename from ui/ts/tests/features/truth-auctions/truthAuctionBidsSection.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/truthAuctionBidsSection.test.tsx index b96029707..2488da1db 100644 --- a/ui/ts/tests/features/truth-auctions/truthAuctionBidsSection.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/truthAuctionBidsSection.test.tsx @@ -4,9 +4,9 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import type { ComponentChildren } from 'preact' import type { Address } from '@zoltar/shared/ethereum' import { TruthAuctionBidsSection, ViewerTruthAuctionBidsSection } from '../../../features/truth-auctions/components/TruthAuctionBidsSection.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { fireEvent, within } from '../../testUtils/queries.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' const walletAddress: Address = '0x0000000000000000000000000000000000000001' diff --git a/ui/ts/tests/features/truth-auctions/truthAuctionHooks.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/truthAuctionHooks.test.tsx similarity index 98% rename from ui/ts/tests/features/truth-auctions/truthAuctionHooks.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/truthAuctionHooks.test.tsx index e6c109d62..4cf77ca12 100644 --- a/ui/ts/tests/features/truth-auctions/truthAuctionHooks.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/truthAuctionHooks.test.tsx @@ -9,11 +9,11 @@ import { useTruthAuctionBookData } from '../../../features/truth-auctions/hooks/ import { useTruthAuctionSettlementActionState } from '../../../features/truth-auctions/hooks/useTruthAuctionSettlementActionState.js' import type { TruthAuctionBidDisposition } from '../../../features/truth-auctions/lib/truthAuctionBook.js' import { getTruthAuctionSettlementBidKey, type TruthAuctionSettlementBidRow } from '../../../features/truth-auctions/lib/truthAuctionSettlement.js' -import type { ForkAuctionActionResult, ReadClient, TruthAuctionBidView } from '../../../types/contracts.js' -import type { SettlementSelectedBid } from '../../../features/types.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { waitFor } from '../../testUtils/queries.js' +import type { ForkAuctionActionResult, ReadClient, TruthAuctionBidView } from '@zoltar/ui-core-shared/types/contracts.js' +import type { SettlementSelectedBid } from '@zoltar/ui-zoltar/features/types.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' const walletAddress: Address = '0x0000000000000000000000000000000000000001' const otherWalletAddress: Address = '0x0000000000000000000000000000000000000002' diff --git a/ui/ts/tests/features/truth-auctions/truthAuctionMarketViewSection.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/truthAuctionMarketViewSection.test.tsx similarity index 87% rename from ui/ts/tests/features/truth-auctions/truthAuctionMarketViewSection.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/truthAuctionMarketViewSection.test.tsx index 85959431b..e0b159411 100644 --- a/ui/ts/tests/features/truth-auctions/truthAuctionMarketViewSection.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/truthAuctionMarketViewSection.test.tsx @@ -1,10 +1,10 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { within } from '../../testUtils/queries.js' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { TruthAuctionMarketViewSection } from '../../../features/truth-auctions/components/TruthAuctionMarketViewSection.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' describe('TruthAuctionMarketViewSection', () => { let restoreDomEnvironment: (() => void) | undefined diff --git a/ui/ts/tests/features/truth-auctions/truthAuctionSettlementActionState.test.ts b/ui/statoblast/ts/tests/features/truth-auctions/truthAuctionSettlementActionState.test.ts similarity index 100% rename from ui/ts/tests/features/truth-auctions/truthAuctionSettlementActionState.test.ts rename to ui/statoblast/ts/tests/features/truth-auctions/truthAuctionSettlementActionState.test.ts diff --git a/ui/ts/tests/features/truth-auctions/useForkAuctionInteractionState.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/useForkAuctionInteractionState.test.tsx similarity index 95% rename from ui/ts/tests/features/truth-auctions/useForkAuctionInteractionState.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/useForkAuctionInteractionState.test.tsx index 1f081601f..0e82e3671 100644 --- a/ui/ts/tests/features/truth-auctions/useForkAuctionInteractionState.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/useForkAuctionInteractionState.test.tsx @@ -5,8 +5,8 @@ import { useState } from 'preact/hooks' import { act } from 'preact/test-utils' import type { Address } from '@zoltar/shared/ethereum' import { useForkAuctionInteractionState } from '../../../features/truth-auctions/hooks/useForkAuctionInteractionState.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' type InteractionProps = Parameters[0] type InteractionState = ReturnType diff --git a/ui/ts/tests/features/truth-auctions/useForkAuctionOperations.test.tsx b/ui/statoblast/ts/tests/features/truth-auctions/useForkAuctionOperations.test.tsx similarity index 98% rename from ui/ts/tests/features/truth-auctions/useForkAuctionOperations.test.tsx rename to ui/statoblast/ts/tests/features/truth-auctions/useForkAuctionOperations.test.tsx index 1749ec2d9..0ab2ba071 100644 --- a/ui/ts/tests/features/truth-auctions/useForkAuctionOperations.test.tsx +++ b/ui/statoblast/ts/tests/features/truth-auctions/useForkAuctionOperations.test.tsx @@ -4,17 +4,17 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, zeroHash, type Address } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { createInitialTransactionTrayState, markTransactionCanceled, markTransactionFailed, markTransactionFinished, markTransactionPrepared, markTransactionRequested, markTransactionSubmitted } from '../../../lib/transactionTray.js' -import { parseTruthAuctionAmountInput, parseTruthAuctionPriceInput } from '../../../features/markets/lib/marketForm.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createInitialTransactionTrayState, markTransactionCanceled, markTransactionFailed, markTransactionFinished, markTransactionPrepared, markTransactionRequested, markTransactionSubmitted } from '@zoltar/ui-core-shared/lib/transactionTray.js' +import { parseTruthAuctionAmountInput, parseTruthAuctionPriceInput } from '@zoltar/ui-core-shared/lib/formInputs.js' import { getTruthAuctionTickAtPrice } from '../../../features/truth-auctions/lib/truthAuctionBook.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { waitFor } from '../../testUtils/queries' -import type { ForkAuctionActionResult, ForkAuctionDetails, MarketDetails } from '../../../types/contracts.js' -import type { SettlementSelectedBid, TransactionIntent } from '../../../features/types.js' -import type { TruthAuctionMetrics } from '../../../types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import type { ForkAuctionActionResult, ForkAuctionDetails, MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import type { SettlementSelectedBid, TransactionIntent } from '@zoltar/ui-zoltar/features/types.js' +import type { TruthAuctionMetrics } from '@zoltar/ui-core-shared/types/contracts.js' import { useForkAuctionOperations, type UseForkAuctionOperationsDependencies } from '../../../features/truth-auctions/hooks/useForkAuctionOperations.js' type UseForkAuctionOperationsState = ReturnType diff --git a/ui/statoblast/ts/tests/protocol/deployment.test.ts b/ui/statoblast/ts/tests/protocol/deployment.test.ts new file mode 100644 index 000000000..d8e91a4d2 --- /dev/null +++ b/ui/statoblast/ts/tests/protocol/deployment.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from 'bun:test' +import { MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE, createSimulationProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { getDeploymentSteps } from '../../protocol/deployment.js' + +const simulationProfile = createSimulationProfile({ genesisRepTokenAddress: '0x1000000000000000000000000000000000000001', wethAddress: '0x2000000000000000000000000000000000000002' }) + +describe('statoblast deployment steps', () => { + test('the deployment status oracle step deploys the statoblast oracle, not the zoltar-only oracle', async () => { + for (const profile of [MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE, simulationProfile]) { + const steps = getDeploymentSteps(profile) + const oracleStep = steps.find(step => step.id === 'deploymentStatusOracle') + if (oracleStep === undefined) throw new Error(`Missing deployment status oracle step for ${profile.id}`) + const statoblastStepIds = ['securityPoolForker', 'escalationGameClaimDelegate', 'escalationGameFactory', 'securityPoolFactory'] + for (const stepId of statoblastStepIds) { + if (steps.every(step => step.id !== stepId)) throw new Error(`Missing statoblast deployment step ${stepId} for ${profile.id}`) + } + // The oracle address is derived from its init bytecode, which embeds the + // monitored contract list. The statoblast list includes the four extra + // statoblast contracts, so the address must differ from the zoltar-only one. + const zoltarOracle = (await import('@zoltar/ui-zoltar/protocol/deployment.js')).getDeploymentSteps(profile).find(step => step.id === 'deploymentStatusOracle') + if (zoltarOracle === undefined) throw new Error('Missing zoltar oracle step') + expect(oracleStep.address === zoltarOracle.address).toBe(false) + } + }) + + test('the oracle step monitors every deployed contract in the plan', async () => { + const profile = simulationProfile + const steps = getDeploymentSteps(profile) + // Deploy through a fake client to capture the oracle init bytecode args. + const oracleStep = steps.find(step => step.id === 'deploymentStatusOracle') + if (oracleStep === undefined) throw new Error('Missing deployment status oracle step') + // The statoblast oracle step address must match the address the snapshot reader + // resolves; both derive from the same bytecode, which embeds the monitored list. + // Regression: previously the zoltar step was reused, so the deployed oracle only + // tracked zoltar contracts and statoblast always reported SETUP INCOMPLETE. + const zoltarSteps = await import('@zoltar/ui-zoltar/protocol/deployment.js') + const zoltarOracle = zoltarSteps.getDeploymentSteps(profile).find(step => step.id === 'deploymentStatusOracle') + if (zoltarOracle === undefined) throw new Error('Missing zoltar oracle step') + expect(oracleStep.address === zoltarOracle.address).toBe(false) + // And every statoblast step must appear in the monitored count: 15 steps for the + // simulation profile (11 zoltar + 4 statoblast), one oracle address per plan. + expect(steps.length).toBe(15) + }) +}) diff --git a/ui/ts/tests/protocol/forks.test.ts b/ui/statoblast/ts/tests/protocol/forks.test.ts similarity index 96% rename from ui/ts/tests/protocol/forks.test.ts rename to ui/statoblast/ts/tests/protocol/forks.test.ts index 49bd3d6a8..eac7dd3b4 100644 --- a/ui/ts/tests/protocol/forks.test.ts +++ b/ui/statoblast/ts/tests/protocol/forks.test.ts @@ -2,10 +2,13 @@ import { describe, expect, test } from 'bun:test' import { decodeFunctionData, getAddress, zeroAddress, type Address, type Hex } from '@zoltar/shared/ethereum' -import { depositRepToVaultToSecurityPool, finalizeSecurityPoolTruthAuction, loadForkAuctionDetails, migrateSharesFromUniverse } from '../../protocol/index.js' -import { getForkOutcomeKey } from '../../protocol/helpers.js' -import { statoblast_tokens_ShareToken_ShareToken } from '../../contractArtifact.js' -import { asWriteClient, createBlockWithTimestamp, createMockLoaderClient, createMockWriteClient, getContractFunctionName } from './testSupport.js' +import { depositRepToVaultToSecurityPool } from '../../protocol/securityVault.js' +import { finalizeSecurityPoolTruthAuction } from '../../protocol/truthAuctionActions.js' +import { migrateSharesFromUniverse } from '../../protocol/trading.js' +import { loadForkAuctionDetails } from '@zoltar/ui-zoltar/protocol/forks.js' +import { getForkOutcomeKey } from '@zoltar/ui-zoltar/protocol/helpers.js' +import { statoblast_tokens_ShareToken_ShareToken } from '@zoltar/ui-core-shared/contractArtifact.js' +import { asWriteClient, createBlockWithTimestamp, createMockLoaderClient, createMockWriteClient, getContractFunctionName } from '@zoltar/ui-core-shared/tests/testUtils/protocolTestSupport.js' const securityPoolAddress = getAddress('0x00000000000000000000000000000000000000a1') const shareTokenAddress = getAddress('0x00000000000000000000000000000000000000b2') diff --git a/ui/ts/tests/protocol/securityPools.test.ts b/ui/statoblast/ts/tests/protocol/securityPools.test.ts similarity index 99% rename from ui/ts/tests/protocol/securityPools.test.ts rename to ui/statoblast/ts/tests/protocol/securityPools.test.ts index 4d693d3fa..552671df9 100644 --- a/ui/ts/tests/protocol/securityPools.test.ts +++ b/ui/statoblast/ts/tests/protocol/securityPools.test.ts @@ -2,8 +2,9 @@ import { describe, expect, test } from 'bun:test' import { bigintToSafeNumber, getAddress, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { loadAllSecurityPools, loadSecurityPoolMintCapacity, loadSecurityPoolPage } from '../../protocol/index.js' -import { createBlockWithTimestamp, createMockLoaderClient, createMulticallStub, getContractFunctionName } from './testSupport.js' +import { loadAllSecurityPools, loadSecurityPoolPage } from '../../protocol/securityPools.js' +import { loadSecurityPoolMintCapacity } from '../../protocol/trading.js' +import { createBlockWithTimestamp, createMockLoaderClient, createMulticallStub, getContractFunctionName } from '@zoltar/ui-core-shared/tests/testUtils/protocolTestSupport.js' const securityPoolAddress = getAddress('0x00000000000000000000000000000000000000a1') const vaultAddress = getAddress('0x00000000000000000000000000000000000000c1') diff --git a/ui/ts/tests/protocol/truthAuctions.test.ts b/ui/statoblast/ts/tests/protocol/truthAuctions.test.ts similarity index 98% rename from ui/ts/tests/protocol/truthAuctions.test.ts rename to ui/statoblast/ts/tests/protocol/truthAuctions.test.ts index c3f8f1126..06d4d3558 100644 --- a/ui/ts/tests/protocol/truthAuctions.test.ts +++ b/ui/statoblast/ts/tests/protocol/truthAuctions.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from 'bun:test' import { getAddress } from '@zoltar/shared/ethereum' -import { loadTruthAuctionActiveTickPage, loadTruthAuctionBidderBidPage, loadTruthAuctionTickBidPage, loadTruthAuctionTickPage, loadTruthAuctionTickSummary } from '../../protocol/index.js' -import { createMockReadClient } from './testSupport.js' +import { loadTruthAuctionActiveTickPage, loadTruthAuctionBidderBidPage, loadTruthAuctionTickBidPage, loadTruthAuctionTickPage, loadTruthAuctionTickSummary } from '../../protocol/truthAuctions.js' +import { createMockReadClient } from '@zoltar/ui-core-shared/tests/testUtils/protocolTestSupport.js' const securityPoolAddress = getAddress('0x00000000000000000000000000000000000000a1') const truthAuctionAddress = getAddress('0x00000000000000000000000000000000000000f6') diff --git a/ui/ts/tests/simulation/simulationBootstrap.test.ts b/ui/statoblast/ts/tests/simulation/bootstrap.test.ts similarity index 94% rename from ui/ts/tests/simulation/simulationBootstrap.test.ts rename to ui/statoblast/ts/tests/simulation/bootstrap.test.ts index 858d9debe..07a7415ff 100644 --- a/ui/ts/tests/simulation/simulationBootstrap.test.ts +++ b/ui/statoblast/ts/tests/simulation/bootstrap.test.ts @@ -1,9 +1,11 @@ /// import { afterEach, describe, expect, mock, test } from 'bun:test' -import { MAINNET_NETWORK_PROFILE, MAINNET_WETH_ADDRESS, type NetworkProfile } from '../../lib/networkProfile.js' -import { SIMULATION_INITIAL_TIMESTAMP } from '../../simulation/clock.js' -import { bootstrapSimulationChain, mintSimulationGenesisRep, predictSimulationTokenAddresses } from '../../simulation/bootstrap.js' +import { MAINNET_NETWORK_PROFILE, MAINNET_WETH_ADDRESS, type NetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { SIMULATION_INITIAL_TIMESTAMP } from '@zoltar/ui-core-shared/simulation/clock.js' +import { bootstrapSimulationChain, mintSimulationGenesisRep, predictSimulationTokenAddresses, type BootstrapScenarioApplyParameters } from '@zoltar/ui-core-shared/simulation/bootstrap.js' +import { applyStatoblastScenario, installStatoblastScenarioProtocolForTesting } from '../../simulation/statoblastScenarios.js' +import type { DeploymentStep } from '@zoltar/ui-core-shared/types/contracts.js' import { type Address, getAddress, getCreateAddress, toHex, zeroAddress } from '@zoltar/shared/ethereum' const MOCK_PRIMARY_ACCOUNT = getAddress('0x00000000000000000000000000000000000000a1') @@ -237,7 +239,7 @@ function createMockedBootstrapDependencies({ accounts, scenario, profile }: { ac delete pendingReportIds[managerAddress] } - mock.module('../../protocol/index.js', () => ({ + const protocolModule = { approveErc20: mock(async () => { state.callLog.approveErc20 += 1 return { @@ -571,9 +573,10 @@ function createMockedBootstrapDependencies({ accounts, scenario, profile }: { ac universeId: 1n, } as never }), - })) + } + installStatoblastScenarioProtocolForTesting(protocolModule as never) - mock.module('../../simulation/clock.js', () => ({ + mock.module('@zoltar/ui-core-shared/simulation/clock.js', () => ({ advanceSimulationTime: async () => undefined, getSimulationChainTimestamp: async () => 1_000n, initializeSimulationClock: async () => 1_000n, @@ -657,7 +660,7 @@ function createMockedBootstrapDependencies({ accounts, scenario, profile }: { ac }, }) as never - return { contractWriteCalls, createWriteClient, memoryClient, state, writeCalls } + return { applyScenario, contractWriteCalls, createWriteClient, getDeploymentSteps: protocolModule.getDeploymentSteps, memoryClient, state, writeCalls } } function createBootstrapMemoryClient( @@ -691,9 +694,19 @@ function createBootstrapMemoryClient( } as never } +const applyScenario = (parameters: BootstrapScenarioApplyParameters) => applyStatoblastScenario(parameters) +const ZOLTAR_SIMULATION_STEP: DeploymentStep = { + id: 'zoltar', + label: 'Zoltar', + address: getAddress('0x00000000000000000000000000000000000000a1'), + deploy: async () => ({ action: 'none', address: getAddress('0x00000000000000000000000000000000000000a1'), hash: '0x01' }) as never, +} +const getDeploymentSteps = () => [ZOLTAR_SIMULATION_STEP] as const satisfies ReadonlyArray + describe('simulation bootstrap', () => { afterEach(() => { mock.restore() + installStatoblastScenarioProtocolForTesting(undefined) }) test('predicts simulation token addresses from account nonces', () => { @@ -822,10 +835,12 @@ describe('simulation bootstrap', () => { }) as never await bootstrapSimulationChain({ + applyScenario, accounts: [MOCK_PRIMARY_ACCOUNT, MOCK_SECONDARY_ACCOUNT], createReadClient: () => ({}) as never, createWriteClient, memoryClient, + getDeploymentSteps, onProgress: progress, primaryAccount: MOCK_PRIMARY_ACCOUNT, profile, @@ -856,10 +871,12 @@ describe('simulation bootstrap', () => { await expect( bootstrapSimulationChain({ + applyScenario, accounts: [], createReadClient: () => ({ kind: 'read-client' }) as never, createWriteClient, memoryClient, + getDeploymentSteps, primaryAccount: MOCK_PRIMARY_ACCOUNT, onProgress: () => undefined, profile: createBaselineProfile(), @@ -884,10 +901,12 @@ describe('simulation bootstrap', () => { await expect( bootstrapSimulationChain({ + applyScenario, accounts: [MOCK_PRIMARY_ACCOUNT], createReadClient: () => ({ kind: 'read-client' }) as never, createWriteClient, memoryClient, + getDeploymentSteps, primaryAccount: MOCK_PRIMARY_ACCOUNT, onProgress: () => undefined, profile: createBaselineProfile(), @@ -914,10 +933,12 @@ describe('simulation bootstrap', () => { }) as never await bootstrapSimulationChain({ + applyScenario, accounts: [MOCK_PRIMARY_ACCOUNT, MOCK_SECONDARY_ACCOUNT], createReadClient: () => ({ kind: 'read-client' }) as never, createWriteClient, memoryClient, + getDeploymentSteps, primaryAccount: MOCK_PRIMARY_ACCOUNT, onProgress: payload => { progressCalls.push(payload.label) @@ -955,10 +976,12 @@ describe('simulation bootstrap', () => { try { await expect( bootstrapSimulationChain({ + applyScenario, accounts: [MOCK_PRIMARY_ACCOUNT, MOCK_SECONDARY_ACCOUNT], createReadClient: () => ({}) as never, createWriteClient: createWriteClient, memoryClient, + getDeploymentSteps, onProgress: () => undefined, primaryAccount: MOCK_PRIMARY_ACCOUNT, profile: createBaselineProfile(), @@ -972,20 +995,22 @@ describe('simulation bootstrap', () => { test('boots the security-pool simulation path with seeded pool construction and oracle report settling', async () => { const profile = createBaselineProfile() - const { contractWriteCalls, createWriteClient, memoryClient, state, writeCalls } = createMockedBootstrapDependencies({ + const { applyScenario, contractWriteCalls, createWriteClient, getDeploymentSteps, memoryClient, state, writeCalls } = createMockedBootstrapDependencies({ accounts: [MOCK_PRIMARY_ACCOUNT], scenario: 'security-pool', profile, }) - const { bootstrapSimulationChain } = await import(`../../simulation/bootstrap.js?case=${crypto.randomUUID()}`) + const { bootstrapSimulationChain } = await import(`@zoltar/ui-core-shared/simulation/bootstrap.js?case=${crypto.randomUUID()}`) const progressCalls: string[] = [] await bootstrapSimulationChain({ + applyScenario, accounts: [MOCK_PRIMARY_ACCOUNT], createReadClient: () => ({}) as never, createWriteClient, memoryClient, + getDeploymentSteps, onProgress: async (payload: { label: string; value: number }) => { progressCalls.push(payload.label) }, @@ -1007,18 +1032,20 @@ describe('simulation bootstrap', () => { test('boots the securitypoolx2 simulation path with secondary vault capacity ownership execution', async () => { const profile = createBaselineProfile() - const { createWriteClient, memoryClient, state, writeCalls } = createMockedBootstrapDependencies({ + const { applyScenario, createWriteClient, getDeploymentSteps, memoryClient, state, writeCalls } = createMockedBootstrapDependencies({ accounts: [MOCK_PRIMARY_ACCOUNT, MOCK_SECONDARY_ACCOUNT], scenario: 'securitypoolx2', profile, }) - const { bootstrapSimulationChain } = await import(`../../simulation/bootstrap.js?case=${crypto.randomUUID()}`) + const { bootstrapSimulationChain } = await import(`@zoltar/ui-core-shared/simulation/bootstrap.js?case=${crypto.randomUUID()}`) await bootstrapSimulationChain({ + applyScenario, accounts: [MOCK_PRIMARY_ACCOUNT, MOCK_SECONDARY_ACCOUNT], createReadClient: () => ({}) as never, createWriteClient, memoryClient, + getDeploymentSteps, onProgress: async () => undefined, primaryAccount: MOCK_PRIMARY_ACCOUNT, profile, @@ -1041,18 +1068,20 @@ describe('simulation bootstrap', () => { test('boots the securitypoolx2-auction simulation path with forked child-auction seeding and ten bids', async () => { const profile = createBaselineProfile() - const { createWriteClient, memoryClient, state, writeCalls } = createMockedBootstrapDependencies({ + const { applyScenario, createWriteClient, getDeploymentSteps, memoryClient, state, writeCalls } = createMockedBootstrapDependencies({ accounts: [MOCK_PRIMARY_ACCOUNT, MOCK_SECONDARY_ACCOUNT], scenario: 'securitypoolx2-auction', profile, }) - const { bootstrapSimulationChain } = await import(`../../simulation/bootstrap.js?case=${crypto.randomUUID()}`) + const { bootstrapSimulationChain } = await import(`@zoltar/ui-core-shared/simulation/bootstrap.js?case=${crypto.randomUUID()}`) await bootstrapSimulationChain({ + applyScenario, accounts: [MOCK_PRIMARY_ACCOUNT, MOCK_SECONDARY_ACCOUNT], createReadClient: () => ({}) as never, createWriteClient, memoryClient, + getDeploymentSteps, onProgress: async () => undefined, primaryAccount: MOCK_PRIMARY_ACCOUNT, profile, diff --git a/ui/statoblast/ts/tests/simulation/scenarios.test.ts b/ui/statoblast/ts/tests/simulation/scenarios.test.ts new file mode 100644 index 000000000..0956bd417 --- /dev/null +++ b/ui/statoblast/ts/tests/simulation/scenarios.test.ts @@ -0,0 +1,33 @@ +/// + +import { describe, expect, test } from 'bun:test' +import { getStatoblastScenarioDescription, getStatoblastScenarioLabel, isStatoblastScenario, registerStatoblastSimulationScenarios } from '../../simulation/index.js' +import { getRegisteredSimulationScenarios, getSimulationScenarioDescription, getSimulationScenarioLabel } from '@zoltar/ui-core-shared/simulation/scenarios.js' + +void describe('statoblast simulation scenarios', () => { + void test('recognizes statoblast scenarios', () => { + expect(isStatoblastScenario('security-pool')).toBe(true) + expect(isStatoblastScenario('securitypoolx2')).toBe(true) + expect(isStatoblastScenario('securitypoolx2-auction')).toBe(true) + expect(isStatoblastScenario('baseline')).toBe(false) + }) + + void test('returns labels for statoblast scenarios', () => { + expect(getStatoblastScenarioLabel('security-pool')).toBe('Security pool') + expect(getStatoblastScenarioLabel('securitypoolx2')).toBe('Security pool x2') + expect(getStatoblastScenarioLabel('securitypoolx2-auction')).toBe('Security pool x2 auction') + }) + + void test('returns descriptions for statoblast scenarios', () => { + expect(getStatoblastScenarioDescription('security-pool')).toBe('One seeded question, one security pool, and one funded vault with an active capacity ownership. Use it to test pool actions and liquidation paths.') + expect(getStatoblastScenarioDescription('securitypoolx2')).toBe('Two seeded questions with two security pools and two funded vaults in each pool. Use it to test multi-pool selection and repeated pool actions.') + expect(getStatoblastScenarioDescription('securitypoolx2-auction')).toBe('Two seeded questions with one own-escalation fork already triggered and one child truth auction seeded with ten bids. Use it to test the fork-auction bidbook and settlement actions.') + }) + + void test('registers scenarios into the shared registry', () => { + registerStatoblastSimulationScenarios() + expect(getRegisteredSimulationScenarios()).toContain('securitypoolx2-auction') + expect(getSimulationScenarioLabel('securitypoolx2-auction')).toBe('Security pool x2 auction') + expect(getSimulationScenarioDescription('security-pool')).toContain('seeded question') + }) +}) diff --git a/ui/ts/tests/simulation/securityPoolEnvironments.test.ts b/ui/statoblast/ts/tests/simulation/securityPoolEnvironments.test.ts similarity index 85% rename from ui/ts/tests/simulation/securityPoolEnvironments.test.ts rename to ui/statoblast/ts/tests/simulation/securityPoolEnvironments.test.ts index 1bbf1c553..cb9dff1e7 100644 --- a/ui/ts/tests/simulation/securityPoolEnvironments.test.ts +++ b/ui/statoblast/ts/tests/simulation/securityPoolEnvironments.test.ts @@ -1,9 +1,10 @@ /// import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test' -import { loadAllSecurityPools, loadSecurityVaultDetails } from '../../protocol/index.js' -import type { SimulationScenario } from '../../simulation/scenarios.js' -import { activateSimulationBackendProfile, createBootstrappedSimulationBackendWithRetry, type SimulationBackend } from './testUtils.js' +import { resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { loadAllSecurityPools, loadSecurityVaultDetails } from '../../protocol/securityPools.js' +import type { SimulationScenario } from '@zoltar/ui-core-shared/simulation/scenarios.js' +import { activateSimulationBackendProfile, createBootstrappedSimulationBackendWithRetry, type SimulationBackend } from '@zoltar/ui-core-shared/tests/simulationTestUtils.js' const SEEDED_REP_DEPOSIT = 10_000n * 10n ** 18n const SEEDED_CAPACITY_OWNERSHIP_ATTO_REP = 80n * 10n ** 18n @@ -14,7 +15,7 @@ void describe('security-pool simulation backends', () => { let securityPoolX2AuctionBackend: SimulationBackend beforeAll(async () => { - const backends = await Promise.all([createBootstrappedSimulationBackendWithRetry('security-pool', 1), createBootstrappedSimulationBackendWithRetry('securitypoolx2', 1), createBootstrappedSimulationBackendWithRetry('securitypoolx2-auction', 1)]) + const backends = await Promise.all([createBootstrappedSimulationBackendWithRetry('security-pool', 1, 'statoblast'), createBootstrappedSimulationBackendWithRetry('securitypoolx2', 1, 'statoblast'), createBootstrappedSimulationBackendWithRetry('securitypoolx2-auction', 1, 'statoblast')]) const [nextSecurityPoolBackend, nextSecurityPoolX2Backend, nextSecurityPoolX2AuctionBackend] = backends if (nextSecurityPoolBackend === undefined || nextSecurityPoolX2Backend === undefined || nextSecurityPoolX2AuctionBackend === undefined) { throw new Error('Expected every seeded security-pool simulation backend') @@ -35,6 +36,7 @@ void describe('security-pool simulation backends', () => { if (securityPoolBackend !== undefined) await securityPoolBackend.dispose() if (securityPoolX2Backend !== undefined) await securityPoolX2Backend.dispose() if (securityPoolX2AuctionBackend !== undefined) await securityPoolX2AuctionBackend.dispose() + resetActiveEnvironmentForTesting() }, 30_000) void test('bootstraps seeded security-pool scenarios without reverting', async () => { diff --git a/ui/statoblast/ts/types/app.ts b/ui/statoblast/ts/types/app.ts new file mode 100644 index 000000000..d904a7c9d --- /dev/null +++ b/ui/statoblast/ts/types/app.ts @@ -0,0 +1,58 @@ +import type { MarketType, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' + +export type { AccountState, WriteOperationsParameters } from '@zoltar/ui-core-shared/types/app.js' + +export type Route = 'deploy' | 'security-pools' | 'open-oracle' | 'not-found' + +export type MarketFormState = { + answerUnit: string + categoricalOutcomes: string[] + description: string + scalarIncrement: string + scalarMax: string + scalarMin: string + title: string + endTime: string + marketType: MarketType + startTime: string +} + +export type SecurityPoolFormState = { + initialReportPriorityFeeGwei: string + marketId: string + statoblastSecurityMultiplierBps: string +} + +export type SecurityVaultFormState = { + depositAmount: string + targetHealthFactor: string + repWithdrawAmount: string + selectedVaultOwner: string + securityPoolAddress: string + stagedOperationTimeoutMinutes?: string +} + +export type TradingFormState = { + completeSetAmount: string + redeemAmount: string + securityPoolAddress: string + selectedShareOutcome: ReportingOutcomeKey + targetOutcomeIndexes: string +} + +export type ForkAuctionFormState = { + claimBidIndex: string + claimBidTick: string + depositIndexes: string + directForkQuestionId: string + directForkUniverseId: string + refundBidIndex: string + refundTick: string + repMigrationOutcomes: string + securityPoolAddress: string + selectedOutcome: ReportingOutcomeKey + settlementAddress: string + submitBidAmount: string + submitBidPrice: string + vaultAddress: string +} diff --git a/ui/statoblast/tsconfig.json b/ui/statoblast/tsconfig.json new file mode 100644 index 000000000..b27b5de1d --- /dev/null +++ b/ui/statoblast/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@zoltar/shared/*": [ + "../../shared/js/*" + ], + "@zoltar/ui-core-shared/*": [ + "../coreShared/js/*" + ], + "@zoltar/ui-zoltar/*": [ + "../zoltar/js/*" + ] + }, + "rootDir": "./ts", + "outDir": "./js" + }, + "include": [ + "./ts/**/*.ts", + "./ts/**/*.tsx" + ], + "exclude": [ + "./ts/tests/**/*.ts", + "./ts/tests/**/*.tsx" + ] +} diff --git a/trading/ui/.gitignore b/ui/trading/.gitignore similarity index 100% rename from trading/ui/.gitignore rename to ui/trading/.gitignore diff --git a/ui/trading/AGENTS.md b/ui/trading/AGENTS.md new file mode 100644 index 000000000..906198073 --- /dev/null +++ b/ui/trading/AGENTS.md @@ -0,0 +1,7 @@ +# Trading UI instructions + +The root and `ui/AGENTS.md` instructions apply. This package owns the Trading browser application and its tests. Trading contracts live under `solidity/contracts/trading`, reusable SDK math lives under `shared/ts/trading`, and contract-facing deployment tooling and tests live under `solidity/ts`. + +Consume shared UI primitives and the active chain environment from `ui/coreShared`. Trading may depend on Zoltar and Statoblast, but those packages must not import Trading. Use the shared active backend for every simulated chain read or write instead of creating an HTTP-only client. + +Use `bigint` for chain quantities. The AMM trades only YES and NO. Visible probability language must say that prices are conditional on a valid resolution. Generated artifacts under `js/`, `vendor/`, `dist/`, and `ts/generated/` are build output and must not be hand-edited. diff --git a/trading/ui/build/core-deployments.mts b/ui/trading/build/core-deployments.mts similarity index 100% rename from trading/ui/build/core-deployments.mts rename to ui/trading/build/core-deployments.mts diff --git a/ui/trading/bun.lock b/ui/trading/bun.lock new file mode 100644 index 000000000..cb3ac7437 --- /dev/null +++ b/ui/trading/bun.lock @@ -0,0 +1,1096 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@zoltar/ui-trading", + "dependencies": { + "@preact/signals": "2.0.1", + "@zoltar/shared": "file:../../shared", + "@zoltar/ui-core-shared": "file:../coreShared", + "@zoltar/ui-statoblast": "file:../statoblast", + "@zoltar/ui-zoltar": "file:../zoltar", + "preact": "10.26.4", + "source-map": "0.6.1", + "tevm": "1.0.0-rc.151", + }, + "devDependencies": { + "better-typescript-lib": "2.11.0", + "bun-types": "1.3.11", + "typescript": "5.8.2", + }, + }, + }, + "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", + "brace-expansion": "5.0.9", + "fast-uri": "3.1.5", + "nanoid": "3.3.18", + "postcss": "8.5.23", + "tmp": "0.2.7", + "ws": "8.21.0", + }, + "packages": { + "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], + + "@ark/schema": ["@ark/schema@0.3.3", "", { "dependencies": { "@ark/util": "0.2.2" } }, "sha512-SA4QggzaKHxkNB+faWmhSJbCPMHmGBCpb6dNG3VIt5VOMZhZJJSD76/tOUzQvvJNzztHkTakrTZea+iKsEDcRQ=="], + + "@ark/util": ["@ark/util@0.2.2", "", {}, "sha512-ryZ4+f3SlReQRH9nTFLK5EeU1Pan5ZfS+ACPSk0ir5uujJouFmvOdnkVfeAJAgeOb3kKmUM9kjelv1cwH2ScZg=="], + + "@effect/schema": ["@effect/schema@0.75.5", "", { "dependencies": { "fast-check": "^3.21.0" }, "peerDependencies": { "effect": "^3.9.2" } }, "sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], + + "@ethereumjs/binarytree": ["@ethereumjs/binarytree@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-szugxzYrQHRvlhbu0BrcW/Kw7tg3K0b1HbKCdwRrFhvz80RbGfML2wqDDZhO5NyvHqWpS+Rt9q+qV9LxOHCP6w=="], + + "@ethereumjs/common": ["@ethereumjs/common@10.1.2", "", { "dependencies": { "@ethereumjs/util": "^10.1.2", "eventemitter3": "^5.0.1" } }, "sha512-whWnhqAxwpDy4zWkM6rqMzb8nioZJpiys01N57+HDyanvK5IRzodV5tdMRDt66PD5vDjl2c9K5UcB039gU2Oyw=="], + + "@ethereumjs/evm": ["@ethereumjs/evm@10.1.2", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.2", "@ethereumjs/common": "^10.1.2", "@ethereumjs/statemanager": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "eventemitter3": "^5.0.1" } }, "sha512-yIslek2pcsUQwBJFeeamN529hbNYBtrSuneSCMn4AM71WAx8vntgDbzYoAJsG/whyB9wOVvzHXubju1JXmaEag=="], + + "@ethereumjs/mpt": ["@ethereumjs/mpt@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-dBlXpkP1ssp+AcUxsJUrY72LZuE1JQEp1AZn5mgmbBcd2Gwkpyi57q4zATlZbxrZUxp/K9UIidgqxQWcOCbo5g=="], + + "@ethereumjs/rlp": ["@ethereumjs/rlp@5.0.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA=="], + + "@ethereumjs/statemanager": ["@ethereumjs/statemanager@10.1.2", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.2", "@ethereumjs/common": "^10.1.2", "@ethereumjs/mpt": "^10.1.2", "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@js-sdsl/ordered-map": "^4.4.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-Pkm9VHF19wxT5c0jB+IDiQBv4W42o0wYSS1NLXeRea0D6CDBu0CLo9ihhnQgM368zXPl6JRbLS+YiG8tJkz4aA=="], + + "@ethereumjs/trie": ["@ethereumjs/trie@6.2.1", "", { "dependencies": { "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", "@types/readable-stream": "^2.3.13", "debug": "^4.3.4", "ethereum-cryptography": "^2.2.1", "lru-cache": "10.1.0", "readable-stream": "^3.6.0" } }, "sha512-MguABMVi/dPtgagK+SuY57rpXFP+Ghr2x+pBDy+e3VmMqUY+WGzFu1QWjBb5/iJ7lINk4CI2Uwsih07Nu9sTSg=="], + + "@ethereumjs/tx": ["@ethereumjs/tx@10.1.2", "", { "dependencies": { "@ethereumjs/common": "^10.1.2", "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-bAYK3YaYkk+auzxGfZSRVDbLHvboJNTx8/tV6jaqgPVlrA1QKEEADDEp/EGz+KI4NQmTGxEtXZ8tV/WjniRNww=="], + + "@ethereumjs/util": ["@ethereumjs/util@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-UPBgXtHHfQugoXOSAoeG3jdmPbl37cwV9y3XqTPAnw8tJj8np14TPV2uc5lOs7C2LMF9Ubn66zyaiYxgwGppng=="], + + "@evmts/zevm": ["@evmts/zevm@0.0.0", "", { "dependencies": { "@ethereumjs/common": "^10.1.1", "@ethereumjs/evm": "^10.1.1", "@ethereumjs/trie": "6.2.1", "@ethereumjs/tx": "^10.1.1", "@noble/curves": "^2.2.0", "@noble/hashes": "^2.2.0" }, "optionalDependencies": { "@evmts/zevm-darwin-arm64": "0.0.0", "@evmts/zevm-darwin-x64": "0.0.0", "@evmts/zevm-freebsd-arm64": "0.0.0", "@evmts/zevm-freebsd-x64": "0.0.0", "@evmts/zevm-linux-arm64-gnu": "0.0.0", "@evmts/zevm-linux-arm64-musl": "0.0.0", "@evmts/zevm-linux-x64-gnu": "0.0.0", "@evmts/zevm-linux-x64-musl": "0.0.0", "@evmts/zevm-win32-arm64-msvc": "0.0.0", "@evmts/zevm-win32-ia32-msvc": "0.0.0", "@evmts/zevm-win32-x64-msvc": "0.0.0" } }, "sha512-YQgEGcHaVSHn1zYk6i3sPbO00UR/2XiBP4IKn2Qj0QWBEkjQZlPCOvxXtxqVF/HolPRKu5OxntcbTwfmivwc5Q=="], + + "@evmts/zevm-darwin-arm64": ["@evmts/zevm-darwin-arm64@0.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-g/vvU0csLbv01oFmhejGOj8UtkpQ3+qSMbr4X5gX1Pgq6+z1AWMTHXaKrxbS2NCVLzMW/W+NWs/3dzttTg6IFg=="], + + "@evmts/zevm-darwin-x64": ["@evmts/zevm-darwin-x64@0.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-KU8FSn/0O9xmfhYr5H28bR6lqsLfKx53P1WU30v/dFNo2W3AgHeH/2MbJcERuO7iNz34nN8L7vnYhW9cL19ryg=="], + + "@evmts/zevm-linux-arm64-gnu": ["@evmts/zevm-linux-arm64-gnu@0.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-cXOB4bDR0sesJzoiPaNIXJ7vF9DsaDnBqqW4Hn7sszCY/pV4uL+CAkoZpMS6q/LEw43LJFuA65kLVABtz+e46g=="], + + "@evmts/zevm-linux-x64-gnu": ["@evmts/zevm-linux-x64-gnu@0.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-a6tpXNchDZzI5mElMZ9XFgt6QRZMlBK2sntxe4cABfNTW7h4/Nwn8rJarfXxi7/jF6QqUuOj5wtWUtwiwThGYQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@latticexyz/block-logs-stream": ["@latticexyz/block-logs-stream@2.2.23", "", { "dependencies": { "@latticexyz/common": "2.2.23", "debug": "^4.3.4", "rxjs": "7.5.5" }, "peerDependencies": { "viem": "2.x" } }, "sha512-PbGw6l+bgBNY/DYCy2uMwpBNoK8Y5Qu2CDe17ZP15ATQLdilNEyYIju+867wonGieW7qfHoZRDIH3iqjLgC6zw=="], + + "@latticexyz/common": ["@latticexyz/common@2.2.23", "", { "dependencies": { "@latticexyz/schema-type": "2.2.23", "@solidity-parser/parser": "^0.16.0", "abitype": "1.0.9", "debug": "^4.3.4", "execa": "^9.5.2", "p-queue": "^7.4.1", "p-retry": "^5.1.2", "prettier": "3.2.5", "prettier-plugin-solidity": "1.3.1" }, "peerDependencies": { "@aws-sdk/client-kms": "3.x", "asn1.js": "5.x", "viem": "2.x" }, "optionalPeers": ["@aws-sdk/client-kms", "asn1.js"] }, "sha512-/+73uTmxce6sHg5vQXDPJHDf3kzGE7XOHUC5fxFEBfUO8dqlDzn6t2UTvuAbh9pWsbAPDFBUoILsod8cnPLduA=="], + + "@latticexyz/config": ["@latticexyz/config@2.2.23", "", { "dependencies": { "@ark/util": "0.2.2", "@latticexyz/common": "2.2.23", "@latticexyz/schema-type": "2.2.23", "find-up": "^6.3.0", "tsx": "^4.19.1" }, "peerDependencies": { "viem": "2.x" } }, "sha512-LB70yEbzf2/5ApTc9ftLugS2Ry7jSoy6oZdVPs15/Vt1kWeKSTsleJf+5vx87tMkcK52DSvjLqfo97EJHVpkUg=="], + + "@latticexyz/protocol-parser": ["@latticexyz/protocol-parser@2.2.23", "", { "dependencies": { "@latticexyz/common": "2.2.23", "@latticexyz/config": "2.2.23", "@latticexyz/schema-type": "2.2.23", "abitype": "1.0.9" }, "peerDependencies": { "viem": "2.x" } }, "sha512-ntZ2ILpMaSz/0Lte41WBooIEjmKiE+uo/nBB1EC8B/v/8KXEqblGn1qdhP053j27WeCsf6M442NVkN2S086qFA=="], + + "@latticexyz/schema-type": ["@latticexyz/schema-type@2.2.23", "", { "dependencies": { "abitype": "1.0.9" }, "peerDependencies": { "viem": "2.x" } }, "sha512-xcT2ku5+THLxQf+oMu5Lv6Q6OheI6Aeoc6TzlfVQSyXdRjReUTFXnSM1CXlG2wSrI8vEu+0A6/LdpEY8XcFlpw=="], + + "@latticexyz/store": ["@latticexyz/store@2.2.23", "", { "dependencies": { "@ark/util": "0.2.2", "@latticexyz/common": "2.2.23", "@latticexyz/config": "2.2.23", "@latticexyz/protocol-parser": "2.2.23", "@latticexyz/schema-type": "2.2.23", "abitype": "1.0.9", "arktype": "2.0.0-beta.6", "debug": "^4.3.4" }, "peerDependencies": { "viem": "2.x" }, "optionalPeers": ["viem"] }, "sha512-xKa16GiCoFIj9ZhIyaZmO9v2RAHkQzJqDSF8nuMI65RHmQIMBGhW0g7Qt+Q5FJX7CJA+LziqzNB9YGU9De7Blw=="], + + "@latticexyz/world": ["@latticexyz/world@2.2.23", "", { "dependencies": { "@ark/util": "0.2.2", "@latticexyz/block-logs-stream": "2.2.23", "@latticexyz/common": "2.2.23", "@latticexyz/config": "2.2.23", "@latticexyz/protocol-parser": "2.2.23", "@latticexyz/schema-type": "2.2.23", "@latticexyz/store": "2.2.23", "abitype": "1.0.9", "arktype": "2.0.0-beta.6", "debug": "^4.3.4" }, "peerDependencies": { "viem": "2.x" }, "optionalPeers": ["viem"] }, "sha512-H0MBsUFpim/zwfZbUfon5uadexzFVJlMiCS6ER9m8L1U4WY0iIEYFmT7EUkQK+NAxbF12119v1+GdW3pPBCmQA=="], + + "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + + "@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="], + + "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], + + "@openzeppelin/contracts": ["@openzeppelin/contracts@5.6.1", "", {}, "sha512-Ly6SlsVJ3mj+b18W3R8gNufB7dTICT105fJhodGAGgyC2oqnBAhqSiNDJ8V8DLY05cCz81GLI0CU5vNYA1EC/w=="], + + "@oxc-project/types": ["@oxc-project/types@0.146.0", "", {}, "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA=="], + + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + + "@ponder/utils": ["@ponder/utils@0.2.18", "", { "peerDependencies": { "typescript": ">=5.0.4", "viem": ">=2" }, "optionalPeers": ["typescript"] }, "sha512-pWacQuohfKlKO440zC39C1l6AH+Vx18mca0cPkxM0YnrB70px5hEwLKrc3fU4DMu72j4pljy/Mvfm95FBouYjw=="], + + "@preact/signals": ["@preact/signals@2.0.1", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-f9p/utMgttPb9bJ+UgRBkTDZ9uQiZfX1/gV3pXGWz+yGNQj8MrnG55Xo8MAG4IHcb5UXQO6tvt9ZlsM4A2j+Rw=="], + + "@preact/signals-core": ["@preact/signals-core@1.14.4", "", {}, "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA=="], + + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.5", "", { "os": "android", "cpu": "arm" }, "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.5", "", { "os": "android", "cpu": "arm64" }, "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.5", "", { "os": "linux", "cpu": "arm" }, "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.5", "", { "os": "none", "cpu": "arm64" }, "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.5", "", { "os": "win32", "cpu": "x64" }, "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@scure/base": ["@scure/base@2.0.0", "", {}, "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w=="], + + "@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], + + "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@solidity-parser/parser": ["@solidity-parser/parser@0.16.2", "", { "dependencies": { "antlr4ts": "^0.5.0-alpha.4" } }, "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tevm/actions": ["@tevm/actions@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3", "zod": "^4.1.11" } }, "sha512-A1ICMc83VK/cauNu/47QPg5pj5vcqvNjl+5JdXJMJKFTkisYqTOkwujoh2tabiylCdkOQL0LATP+BmUt4xPUuA=="], + + "@tevm/address": ["@tevm/address@1.0.0-rc.151", "", { "dependencies": { "@tevm/errors": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-w86dds+px4s/KHfbgxYheyuEk+ZPCayzLWIUzEU/vkvpR7hXLwbolFmogncBPv4zfFOB95ZnopVWxyP2TEbFug=="], + + "@tevm/base-bundler": ["@tevm/base-bundler@1.0.0-rc.151", "", { "dependencies": { "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/compiler": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/runtime": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0", "effect": "3.21.2", "solc": "0.8.35", "solidity-ast": "^0.4.62" } }, "sha512-d0X95IYnQOQGRYZ7JoFXwQYsmF1ZEtRVtppbNZBsfHqrL2fi4iuvVZBmoFPxROANZh/Seo3/Nlk3xa0bA0LAMw=="], + + "@tevm/block": ["@tevm/block@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-4O4+MkAMZ7p9wYwPJYgZBO3lrdQtD5pF0QQgB0ZeJMsibIPw32cVKa1Kfcn1yiI6HjKwmthbLcCXmofXTsBFOQ=="], + + "@tevm/blockchain": ["@tevm/blockchain@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/block": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-eYmxpMXIanrFalOqK0Zk9ZaYhEnws41p+eGSk54lEgudGbPEFTkKEJb4dK8+gAHIxEu5SYcUFdTXLHtfTVezyA=="], + + "@tevm/bun-plugin": ["@tevm/bun-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "effect": "3.21.2", "solc": "0.8.35" } }, "sha512-YYbFClA/nrxyMO7aLP794FTMDeGM0JxpA3bnfE5vY5IDKubG3Dy4N7joih2DOF47K7NFDRv2OAsR6anToFlDYA=="], + + "@tevm/bundler-cache": ["@tevm/bundler-cache@1.0.0-rc.151", "", { "dependencies": { "@tevm/compiler": "^1.0.0-rc.151", "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0" } }, "sha512-EvB5i73kc4KYKHmtBxhPSsVKmtQL0bf6iDExjmhZC/lTzSh6fxlZh+pbtx08Pdtqlx7K7b1GjQKy5wjO67YYxQ=="], + + "@tevm/client-types": ["@tevm/client-types@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-w4aNcZY0cRP0gVqOUwh15jx8bBayxWRq3pSBpM65a/UfiTPKZsgoE2/CX/wITh4XEnYW+gYZToQ7GYswiZuxMg=="], + + "@tevm/common": ["@tevm/common@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/errors": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-fcDCjn06lZRebXl2N5DB1yOfEob0oJVaOslWX0SCWP//C/3z2aFaJns2RkGU+bpzdkK/K55lJq7/EOyX1rv7hw=="], + + "@tevm/compiler": ["@tevm/compiler@1.0.0-rc.151", "", { "dependencies": { "@tevm/config": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/resolutions": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@types/node": "^25.9.0", "abitype": "^1.1.1", "effect": "3.21.2", "resolve": "^1.22.12", "solc-typed-ast": "^18.2.5", "solidity-ast": "^0.4.62" } }, "sha512-qm/19mpaLJ+W5sRNYxSEbZ2RpioRF+w9fjbIFTh+HMxvDjoh9W71PtzqE4hi3+Dg5ro8yzC91Hg6TG7mHfCCIg=="], + + "@tevm/config": ["@tevm/config@1.0.0-rc.151", "", { "dependencies": { "@effect/schema": "0.75.5", "@tevm/effect": "^1.0.0-rc.151", "@types/node": "^25.9.0", "effect": "3.21.2", "zod": "^4.4.3" } }, "sha512-u1iUCojpPWywfRiukZttKa9TQUEwM1uITt3zkBlFMz1jDdZf9E5+sjW4GgbYQdD9eOZt49WCkvJg7VWkRTz+QQ=="], + + "@tevm/consensus": ["@tevm/consensus@1.0.0-rc.152", "", {}, "sha512-YtWQ5hWv05v7FY+QGyYS/l3jLnZWYSS8DV72lIXJ9mrTj9kLMadJcAIDB6MyaZcqeRmVr2WNETCGhOauAGs8GQ=="], + + "@tevm/contract": ["@tevm/contract@1.0.0-rc.151", "", { "dependencies": { "@tevm/errors": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-e3e1V9AngTGsZq2/a1vx+lJpY3IL7ITLhI799kVjLCvqZlNs9/l9EEt8NSv8FpU1+boM2HZcpsRclU/9IftJYA=="], + + "@tevm/decorators": ["@tevm/decorators@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-gw/sZXtFRp/BjAJT4He2MbsCmrrNfH3AXZgQ36KiA71Vure6xI6ecrZauknXa3GqquX2tglusFg8FG7J3ifTpw=="], + + "@tevm/effect": ["@tevm/effect@1.0.0-rc.151", "", { "dependencies": { "effect": "3.21.2", "jsonc-parser": "^3.3.1", "resolve": "^1.22.12" } }, "sha512-9yayiyXSQOaF6ThbBVqRIXZ6VmEdrAPvQgclyFnNt2UOt5RmrusakVptPLiZllMzANb6LnFxZ9pjWLB1ZsnOPQ=="], + + "@tevm/errors": ["@tevm/errors@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "viem": "^2.49.3" } }, "sha512-F+l837Rl3d+GcvSbQw1/QooSmqU4lYmB94MSlZfH6tQiomGSKcVrI+G4PDqvZQJzVYPbIHgpZsD/DKA+eE4p5g=="], + + "@tevm/esbuild-plugin": ["@tevm/esbuild-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-/bzaQ1BejzsO/yVARb1S5Z1B3kjL0lC4PNHIYi0a/RbSpMmJzOzL1FqSiSvXCQ7ID7peJEEWPiK2XHzDk6+Tag=="], + + "@tevm/evm": ["@tevm/evm@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-syg7wCmMd4cktBK/pgipjsYuStSZhN54A1BGeTKu8yBYcQh+baL5uwz/MEk/pn9+UIYzo2Q7qbdVYPQD3wpfyw=="], + + "@tevm/http-client": ["@tevm/http-client@1.0.0-rc.151", "", { "dependencies": { "@tevm/address": "1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/viem": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-BTHBxCkg1gaSZx6UOpab+oLanXCw2ikxK67HSI/xYuwL3YpigDNG7BmdYSffL81G6tx66Lj1DgZ5M9AQbY6p5A=="], + + "@tevm/jsonrpc": ["@tevm/jsonrpc@1.0.0-rc.151", "", { "dependencies": { "@ponder/utils": "^0.2.18" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-3oCXYzDpBDWRFMK/fFI+othvFFADmbBUiU9IxEN/KhV019kB3+zfQs7g7NFqey5m4c90pEAB//M16RKT9HvXdQ=="], + + "@tevm/logger": ["@tevm/logger@1.0.0-rc.151", "", { "dependencies": { "pino": "^10.3.1" } }, "sha512-asE32mndV+Eb8tO8TbjgAeELiJIu4dDyjpnsBJJ7vuX9mYDyg899E27bXM9ByvzhpCmKbmufmRstAeg+xXjUGA=="], + + "@tevm/memory-client": ["@tevm/memory-client@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/address": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "kzg-wasm": "^1.0.0", "vitest": "^4.1.6" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-l7SG85t5OLZv17Gu73pVUnG+Y7D+WCp5tkYYiMn0INtbHG5q3MU1iaeubXcNvSSyZAxxwHtX/I7J9X5I7GL/tA=="], + + "@tevm/node": ["@tevm/node@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/evm": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-lJLaX1QARvwzWhG6ly/l1ZP6jK5XnBmARUM9dUYCP+9S/owixjd7CrI4k+AVCVBEi9yajT45o4j2qa0Opcl+BQ=="], + + "@tevm/precompiles": ["@tevm/precompiles@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@noble/curves": "^2.2.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-mABFuxsZ2OhQPKIMAocwkT42cD1G9OI3vBGuQ0dbpheHdbm7hmClmazySd1fV22exBcC+IIhgJYdtOQT3mozCw=="], + + "@tevm/predeploys": ["@tevm/predeploys@1.0.0-rc.151", "", { "dependencies": { "@tevm/address": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-f82mrrr0Ast4+vw1S4XfIv38em70smo4K40CGTFJ7w3a2rRtXdwMKnJNTaucNdnhi4yj6Sih4AMnIK/z+g2M3Q=="], + + "@tevm/receipt-manager": ["@tevm/receipt-manager@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151" } }, "sha512-gDoNRMSzQFtI8QT5A0QX3vUnpEeXLeaeIyyKRBgKPC2PfMYmyLz4RK8r27v4XJSQJPyiUvYVzKL9HA7hH+QL0Q=="], + + "@tevm/resolutions": ["@tevm/resolutions@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0", "effect": "3.21.2", "resolve": "^1.22.12" } }, "sha512-KYQ9tokr523usldSLElPqCzbw1FmL/G4RlAfVR7EndTP+kEIo9iDh3KdeZbXlQHgkYNHpIqY6Smm5qQHRtnntg=="], + + "@tevm/rollup-plugin": ["@tevm/rollup-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-hTc/s4Zki9ibWLF2BsY6Z5WFRdvSlFg2w2hRzHNl9YqmxGoKyFsFLKpTXWhSUdAXWKYSO6CreRWHOIxdiHM3Xw=="], + + "@tevm/rspack-plugin": ["@tevm/rspack-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-FAjBW/o667yGGcW0LSuskQcSPj/QqZkuDM4jb3lMWZwPUh50wKBloxouodFMvg7DJlWbUb/jTycEmwNw08QsTw=="], + + "@tevm/runtime": ["@tevm/runtime@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "effect": "3.21.2" } }, "sha512-eZi4g01PPmTIeS6uBfJQX31YxLEXE3LoRV2z63XcKBrEpe8WwAp/M4Ob90+9+qxtd4hS8mv0CkX1JE5eLhTroQ=="], + + "@tevm/server": ["@tevm/server@1.0.0-rc.151", "", { "dependencies": { "@tevm/common": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "commander": "^14.0.3", "zod": "^4.4.3" } }, "sha512-3BBNPReFgKUnycYzQs0YdgnQ+NXXrTyqSUjoPel6MEic4Iaq/VOqpzq16ykgK+zhryEkVloFAz7oSd7QXOlypw=="], + + "@tevm/solc": ["@tevm/solc@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@tevm/tsupconfig": "^1.0.0-rc.151", "abitype": "^1.2.4", "solidity-ast": "^0.4.61" } }, "sha512-LYNDMNz9JyS+NBMQJJqeSbzaazrDnJ5r2iFtTISFmdGHCDFoXJVNgCMx5ZmpvhrE0XOPueZQGUri1nV25Jsecg=="], + + "@tevm/state": ["@tevm/state@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/address": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/logger": "^1.0.0-rc.151", "@tevm/test-utils": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "ethereum-cryptography": "^3.2.0" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-aSgXdKI34+w/8MaQcjZEg0SSt/zjA+BJkYMnekf3LEIvUaGigvjwEODnxECmq6+yLFoQzQ5vcwFQ1zFiCDX0pQ=="], + + "@tevm/sync-storage-persister": ["@tevm/sync-storage-persister@1.0.0-rc.151", "", { "dependencies": { "@tevm/state": "^1.0.0-rc.151" } }, "sha512-w8yYpE9CUyuR9YMLWXIA+5f4ujtXrvx59STW8iwxzm6Ag4/QfJEf6Bna/DwUSLmoh4xeYrZqrQMYVQDDWvJE5A=="], + + "@tevm/test-utils": ["@tevm/test-utils@1.0.0-rc.151", "", { "dependencies": { "@latticexyz/schema-type": "^2.2.23", "@latticexyz/store": "^2.2.23", "@latticexyz/world": "^2.2.23", "@openzeppelin/contracts": "5.6.1", "@ponder/utils": "^0.2.18", "@tevm/common": "^1.0.0-rc.151", "@tevm/contract": "^1.0.0-rc.151", "@tevm/esbuild-plugin": "^1.0.0-rc.151", "@tevm/ts-plugin": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-AwtMQyxuDi12H1isX6B0MA9gliK+3N5XOfpXFEFydXGiGckSg7i9QRGJjkG5zS6Tw/P0bq9f1VOXmkqfzVYBYA=="], + + "@tevm/ts-plugin": ["@tevm/ts-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "effect": "3.21.2", "glob": "^13.0.6", "minimatch": "^10.2.5", "solc": "0.8.35", "solidity-ast": "^0.4.62" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "tevm-gen": "dist/bin/tevm-gen.js" } }, "sha512-MkglYXhuoQ3W8R167JZvR/aOoZVzQ+7/dZvGzpkfXyuG+vuxoYhMRt+MWesJrBwyr3+siCvJh6lolBMkDopHew=="], + + "@tevm/tsconfig": ["@tevm/tsconfig@1.0.0-rc.151", "", {}, "sha512-N0RFm8DhMLAk3T9EExLroDcY8TVfULzJKDQ1ENy5mVfWXwsssIeoKbOk46sx1L9U9LQt1If2w0MSCcNbo4ihFg=="], + + "@tevm/tsupconfig": ["@tevm/tsupconfig@1.0.0-rc.151", "", { "dependencies": { "@tevm/tsconfig": "^1.0.0-rc.151", "@types/node": "^25.9.0" } }, "sha512-O/6V2iZaA95REre+mZ+FmzYkg82A8m0jt0OvZ3/Oc9VSvGPxaaePVymg1u/nCRCR5tolX7zuags4xKDE8FB1FA=="], + + "@tevm/tx": ["@tevm/tx@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0" } }, "sha512-dxHBYtEvGf+8Q4oAirJI6X2pUeueQ6/FPWjbA2cKxRqtEv9+eMCSRcc+wJbLrSnQX0obTgirWxrLqGhfni0YuA=="], + + "@tevm/txpool": ["@tevm/txpool@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0" } }, "sha512-x01SVbqIzYk2oHi/k33nDN33/CCfPse+aq83DIZQ2nVOovhAijGry6rVewxj3yogY23WITluahxiWYndw0J7WA=="], + + "@tevm/unplugin": ["@tevm/unplugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "effect": "3.21.2", "solc": "0.8.35", "unplugin": "^3.0.0", "zod": "^4.4.3" } }, "sha512-vV2mxwehVAaVF7F/q0eIKdgfOa1p9U4/ncaeZV/cQSdA8VuW/i3fTIxLxb2wGcifaMNygZCJIjF1fyE9hdnRFw=="], + + "@tevm/utils": ["@tevm/utils@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/errors": "^1.0.0-rc.151", "abitype": "^1.2.4" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-lwd2Ue0KY9sI1TYLc+eAwE97XHLIrgDTtckWgoGwWfxeeCbI0T1nh6g3Dkm8WYOK11nzqxZCx/6rFV8TNLYU9A=="], + + "@tevm/viem": ["@tevm/viem@1.0.0-rc.151", "", { "dependencies": { "@tevm/decorators": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-9ur1qqgo/Cvv7xri25+PtL57yqXsWMVIdLZmOLoLbPggkRd8xv31weaYd91LrgvxziuRvALr30Ex+q9qOrNfhg=="], + + "@tevm/vite-plugin": ["@tevm/vite-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" } }, "sha512-5J3jTIj9XsU44w1MzJXk5MJQneV0pbErjf3aH/RlYAie4VUG9q95Lim2hvoOPTavwN3bp3JWW7qHnGDhTPcXLw=="], + + "@tevm/vm": ["@tevm/vm@1.0.0-rc.151", "", { "dependencies": { "@evmts/zevm": "0.0.0", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/evm": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "eventemitter3": "^5.0.4" }, "peerDependencies": { "viem": "^2.49.3" } }, "sha512-o7mjWa29//tsrauRuBS31YfyDFp++rneTEPUsuYmNNAD5356KGwqHA8eZuDsehXW7KBhuhtzRMxa2SuAAj8+Fg=="], + + "@tevm/webpack-plugin": ["@tevm/webpack-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" }, "peerDependencies": { "webpack": ">5.0.0" } }, "sha512-6/3IMmTU932xIrp9oaQZDM/7pQIbfipxSpwjq1rLxy/d0RUOiWiaohmy0vvJBl0s4rRnT+nT5rTn4fJ+lCN04Q=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], + + "@types/readable-stream": ["@types/readable-stream@2.3.15", "", { "dependencies": { "@types/node": "*", "safe-buffer": "~5.1.1" } }, "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ=="], + + "@types/retry": ["@types/retry@0.12.1", "", {}, "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g=="], + + "@typescript/lib-decorators": ["@better-typescript-lib/decorators@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-ZL90jrzAhnvDr/fbpJt3CgGZdWvAeoAWznp56/mHJRKrCwsaJ9uCPqU09D21FJkWsmZ2jkz+mwBI9+e5dzwFvg=="], + + "@typescript/lib-dom": ["@better-typescript-lib/dom@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-tt0ZgcgxqwRL3oWa2xgtBnq7zJjBXqrtwQo6hvHEwsKNA5SD65FVrC+b1zTFTGvTLURGDIlq1kn3cB2quurVAA=="], + + "@typescript/lib-es2015": ["@better-typescript-lib/es2015@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Xtg8WhxWAvBawWBOsAF3XurOHa18A7nvqYdfGYz4EXfrDvw32nod6zjf0it7/sDwcGIcms6wyRZyLkI4RP7e8Q=="], + + "@typescript/lib-es2016": ["@better-typescript-lib/es2016@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-RFLgskFU2rt/5esIcWibJu8n9a4EjmP6hCh5kqULb+w5J0eE9gXnhU6IZ685+spY2nPw991IYwnUP5idtfiGjQ=="], + + "@typescript/lib-es2017": ["@better-typescript-lib/es2017@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-bgCJrI6h3d6jvZsnzsbdH7/HVAV2GYrbtiUnBxV/ZTJ+VDiHdSI8CvsjIAVYHmrzLDC7FBTU+B+O8YhQ+fsehA=="], + + "@typescript/lib-es2018": ["@better-typescript-lib/es2018@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-8rwLcK9SqzO6bCsa6fQkCrYtFMAs8b5Oz+Rhuh/RmKD/Ja6XvkuXvsA8k+Zf6LjzEc/1aHk3ooQaoPeyh5wVIw=="], + + "@typescript/lib-es2019": ["@better-typescript-lib/es2019@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-S16xjOz1GJ1N9c7cdk+jywkkKjagQI8ojP9wBABU4876rv6Ng64iYVBG6IOpPcQ3eSIbnWSZVk4h4kCyJt1sKA=="], + + "@typescript/lib-es2020": ["@better-typescript-lib/es2020@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-TZMzKXvPXrxT25nhuA43uUbpBagwi5jjxfBcMPZQwbDfFAemO0oa96tQCw8L5oKxN/uxFLhRZuEwLRLVrtcjFw=="], + + "@typescript/lib-es2021": ["@better-typescript-lib/es2021@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-TCc6rj/uj3SHO9SPmsUqT3uRuZgFF2+ZLsKmA6+vQZDDVBQnelQHuooUhUWz49dh9TzntEI46x+yXen+UHfBGw=="], + + "@typescript/lib-es2022": ["@better-typescript-lib/es2022@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Gu5MihDWyEgEd7gXXmMmh2lwyZDPlJIEU8v7Mfeqlk7BfYmS4Raww7f1WYGlXT01zAUELE9vOQdOb+jWG+9N1A=="], + + "@typescript/lib-es2023": ["@better-typescript-lib/es2023@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Ezo4X5hM5bZx9LTPwexvpn1dq+JO+Od6P661pVjMoFA1eUm13SxGox2YIeISFAnWmJyLrcA9ZDr9+1cYlbeZzA=="], + + "@typescript/lib-es2024": ["@better-typescript-lib/es2024@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-1Gix0YIF/NkCid7W+/bOMivG3vIUYfOfpUNx94UDRmkTHTQa3/Vu/X2Mjes8Cu60fBhcfscMBC6QY0emStK79w=="], + + "@typescript/lib-es5": ["@better-typescript-lib/es5@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-Efdc4z0hL+HQvcKmsuKtypYcwREUjm1U17eE9J3oXQ2icjR6bAD5IEgFX2gSV2/Ad1KGeBS9gakLxYvDP1G0eQ=="], + + "@typescript/lib-es6": ["@better-typescript-lib/es6@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-zjl9jCTXwWx0QRSv5ILADM1wEgLq40VzBFURVHXg0D8LYv2KYVA7ICdcJZ2/DxoLhaukJm42mllRPjr/Bylw/A=="], + + "@typescript/lib-esnext": ["@better-typescript-lib/esnext@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-NTWWr05QE2/oes0xEiy84on87gzzEWyk0vwPTVgcuWvgtBwDe9sIeGJQSO4vfc7ZP+s3FPBqeSPiAlB7rZi68w=="], + + "@typescript/lib-scripthost": ["@better-typescript-lib/scripthost@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-XwpujOfKtEn0oXodg8Rzm7ULu4OscK0l88ozWp4/q114k5vVd/1d90Q8nEIlB2RR8eqcGNkBG+Tcaj0qiaaJ1A=="], + + "@typescript/lib-webworker": ["@better-typescript-lib/webworker@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-9jdYsSj7YWGixeVok7xZSV+OHpOxMBEcuw+zPHj2462LS812Xq2Vs5Kk00PoFjVXolgQm6naXDG1Wa+x445BPQ=="], + + "@vitest/expect": ["@vitest/expect@4.1.11", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.11", "", { "dependencies": { "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="], + + "@vitest/runner": ["@vitest/runner@4.1.11", "", { "dependencies": { "@vitest/utils": "4.1.11", "pathe": "^2.0.3" } }, "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog=="], + + "@vitest/spy": ["@vitest/spy@4.1.11", "", {}, "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA=="], + + "@vitest/utils": ["@vitest/utils@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ=="], + + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], + + "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], + + "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], + + "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], + + "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], + + "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], + + "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], + + "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], + + "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], + + "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], + + "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], + + "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], + + "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], + + "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], + + "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], + + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], + + "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + + "@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-core-shared": ["@zoltar/ui-core-shared@file:../coreShared", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "@zoltar/ui-statoblast": ["@zoltar/ui-statoblast@file:../statoblast", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "@zoltar/ui-core-shared": "file:../coreShared", "@zoltar/ui-zoltar": "file:../zoltar", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "@zoltar/ui-zoltar": ["@zoltar/ui-zoltar@file:../zoltar", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "@zoltar/ui-core-shared": "file:../coreShared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "abitype": ["abitype@1.3.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-fk6Te+bojIFrMvMZrnOO+SxCB+RUksTGOzq/60ZRvs1L+BVzvi2bqt9L3W/17ZLdZsyM1FuYf65P5nlmoiH1Bg=="], + + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], + + "antlr4ts": ["antlr4ts@0.5.0-alpha.4", "", {}, "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ=="], + + "arktype": ["arktype@2.0.0-beta.6", "", { "dependencies": { "@ark/schema": "0.3.3", "@ark/util": "0.2.2" } }, "sha512-tbH5/h0z371sgrJIAhZhH2BcrErWv8uQIPVcLmknJ8ffov5/ZbMNufrQ3hG9avGKTcVnVmdQoPhl1WuKuagqXA=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA=="], + + "better-typescript-lib": ["better-typescript-lib@2.11.0", "", { "dependencies": { "@typescript/lib-decorators": "npm:@better-typescript-lib/decorators@2.11.0", "@typescript/lib-dom": "npm:@better-typescript-lib/dom@2.11.0", "@typescript/lib-es2015": "npm:@better-typescript-lib/es2015@2.11.0", "@typescript/lib-es2016": "npm:@better-typescript-lib/es2016@2.11.0", "@typescript/lib-es2017": "npm:@better-typescript-lib/es2017@2.11.0", "@typescript/lib-es2018": "npm:@better-typescript-lib/es2018@2.11.0", "@typescript/lib-es2019": "npm:@better-typescript-lib/es2019@2.11.0", "@typescript/lib-es2020": "npm:@better-typescript-lib/es2020@2.11.0", "@typescript/lib-es2021": "npm:@better-typescript-lib/es2021@2.11.0", "@typescript/lib-es2022": "npm:@better-typescript-lib/es2022@2.11.0", "@typescript/lib-es2023": "npm:@better-typescript-lib/es2023@2.11.0", "@typescript/lib-es2024": "npm:@better-typescript-lib/es2024@2.11.0", "@typescript/lib-es5": "npm:@better-typescript-lib/es5@2.11.0", "@typescript/lib-es6": "npm:@better-typescript-lib/es6@2.11.0", "@typescript/lib-esnext": "npm:@better-typescript-lib/esnext@2.11.0", "@typescript/lib-scripthost": "npm:@better-typescript-lib/scripthost@2.11.0", "@typescript/lib-webworker": "npm:@better-typescript-lib/webworker@2.11.0" }, "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-dwaPodcG5lVepyeoEmwRSpRqKWOso5CRktnMcPKlE4jjcHLGjkBhZXIMe6EFD9ALG/Ru3kwOO1t6TNNQ6Pf1Jw=="], + + "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "command-exists": ["command-exists@1.2.9", "", {}, "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="], + + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-file": ["detect-file@1.0.0", "", {}, "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.411", "", {}, "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg=="], + + "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], + + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "ethereum-cryptography": ["ethereum-cryptography@3.2.0", "", { "dependencies": { "@noble/ciphers": "1.3.0", "@noble/curves": "1.9.0", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0" } }, "sha512-Urr5YVsalH+Jo0sYkTkv1MyI9bLYZwW8BENZCeE1QYaTHETEYx0Nv/SVsWkSqpYrzweg6d8KMY1wTjH/1m/BIg=="], + + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + + "expand-tilde": ["expand-tilde@2.0.2", "", { "dependencies": { "homedir-polyfill": "^1.0.1" } }, "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw=="], + + "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="], + + "findup-sync": ["findup-sync@5.0.0", "", { "dependencies": { "detect-file": "^1.0.0", "is-glob": "^4.0.3", "micromatch": "^4.0.4", "resolve-dir": "^1.0.1" } }, "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], + + "fs-extra": ["fs-extra@11.4.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "funtypes": ["funtypes@5.1.1", "", {}, "sha512-JdOe6zAk3kzTTuoTKrdshe16mr8FwuNzUnSg5gZpeJxMYFiY0LaIec9A4/VHK8Z3fSnm09eMrH93vaHE2R0J5Q=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "global-modules": ["global-modules@1.0.0", "", { "dependencies": { "global-prefix": "^1.0.1", "is-windows": "^1.0.1", "resolve-dir": "^1.0.0" } }, "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg=="], + + "global-prefix": ["global-prefix@1.0.2", "", { "dependencies": { "expand-tilde": "^2.0.2", "homedir-polyfill": "^1.0.1", "ini": "^1.3.4", "is-windows": "^1.0.1", "which": "^1.2.14" } }, "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + + "homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="], + + "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], + + "js-sha3": ["js-sha3@0.8.0", "", {}, "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="], + + "jsel": ["jsel@1.1.6", "", {}, "sha512-7E6r8kVzjmKhwXR/82Z+43edfOJGRvLvx6cJZ+SS2MGAPPtYZGnaIsFHpQMA1IbIPA9twDProkob4IIAJ0ZqSw=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "kzg-wasm": ["kzg-wasm@1.0.0", "", {}, "sha512-jMvFK5uiV41BgcLrCaHbNeDeQeZdoTcqOYm7yS1Xdvl4MAykjou+SLlnt+P1bWz5C0YWHOmgUUjIpCuU83zNTA=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], + + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "micro-eth-signer": ["micro-eth-signer@0.18.1", "", { "dependencies": { "@noble/curves": "^2.0.0", "@noble/hashes": "^2.0.0", "micro-packed": "^0.8.0" } }, "sha512-vXKhCZxrytpl+dXR9JeaE41ZVFndi7wCKc1Jd22smOMAeDErvRcXaTxhYUf1yQxis4n2kIdv/pH7iuf+5/cj+Q=="], + + "micro-packed": ["micro-packed@0.8.0", "", { "dependencies": { "@scure/base": "2.0.0" } }, "sha512-AKb8znIvg9sooythbXzyFeChEY0SkW0C6iXECpy/ls0e5BtwXO45J9wD9SLzBztnS4XmF/5kwZknsq+jyynd/A=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "minimizer-webpack-plugin": ["minimizer-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "@minify-html/node": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "lightningcss": "*", "postcss": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "lightningcss", "postcss", "uglify-js"] }, "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "ox": ["ox@0.14.34", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-12seOIk7dv8eAoGQhcWaeKZxNz304IVcDvb9U5Y7JZAEVe21Nm1YMxLjhWah+su5BD4Omx4Zz0z5x3ij9M4GYQ=="], + + "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + + "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + + "p-queue": ["p-queue@7.4.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^5.0.2" } }, "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA=="], + + "p-retry": ["p-retry@5.1.2", "", { "dependencies": { "@types/retry": "0.12.1", "retry": "^0.13.1" } }, "sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g=="], + + "p-timeout": ["p-timeout@5.1.0", "", {}, "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "parse-passwd": ["parse-passwd@1.0.0", "", {}, "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q=="], + + "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], + + "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], + + "preact": ["preact@10.26.4", "", {}, "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w=="], + + "prettier": ["prettier@3.2.5", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A=="], + + "prettier-plugin-solidity": ["prettier-plugin-solidity@1.3.1", "", { "dependencies": { "@solidity-parser/parser": "^0.17.0", "semver": "^7.5.4", "solidity-comments-extractor": "^0.0.8" }, "peerDependencies": { "prettier": ">=2.3.0" } }, "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA=="], + + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + + "process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-dir": ["resolve-dir@1.0.1", "", { "dependencies": { "expand-tilde": "^2.0.0", "global-modules": "^1.0.0" } }, "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg=="], + + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "rolldown": ["rolldown@1.2.5", "", { "dependencies": { "@oxc-project/types": "=0.146.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.5", "@rolldown/binding-android-arm64": "1.2.5", "@rolldown/binding-darwin-arm64": "1.2.5", "@rolldown/binding-darwin-x64": "1.2.5", "@rolldown/binding-freebsd-x64": "1.2.5", "@rolldown/binding-linux-arm-gnueabihf": "1.2.5", "@rolldown/binding-linux-arm64-gnu": "1.2.5", "@rolldown/binding-linux-arm64-musl": "1.2.5", "@rolldown/binding-linux-ppc64-gnu": "1.2.5", "@rolldown/binding-linux-s390x-gnu": "1.2.5", "@rolldown/binding-linux-x64-gnu": "1.2.5", "@rolldown/binding-linux-x64-musl": "1.2.5", "@rolldown/binding-openharmony-arm64": "1.2.5", "@rolldown/binding-win32-arm64-msvc": "1.2.5", "@rolldown/binding-win32-x64-msvc": "1.2.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA=="], + + "rxjs": ["rxjs@7.5.5", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw=="], + + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], + + "semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + + "solc-typed-ast": ["solc-typed-ast@18.2.6", "", { "dependencies": { "axios": "^1.6.8", "commander": "^12.0.0", "decimal.js": "^10.4.3", "findup-sync": "^5.0.0", "fs-extra": "^11.2.0", "jsel": "^1.1.6", "semver": "^7.6.0", "solc": "0.8.25", "src-location": "^1.1.0", "web3-eth-abi": "^4.2.0" }, "bin": { "sol-ast-compile": "dist/bin/compile.js" } }, "sha512-JObIRpPYO4zvQkAgr3koBr5Du+IkYzVNofykZpGv/44iJ0VRsaEq/X+LrrFD0SDqFcnyYcBOPWDiOXwNOStKlg=="], + + "solidity-ast": ["solidity-ast@0.4.62", "", {}, "sha512-jSC7msQCkJXIzM8LlDjRZ5cif5w40g6THlXHFk3zchbL5dm3YLoBETvqPGo5KndYkftjhcs5kz1fnTu4d34lVQ=="], + + "solidity-comments-extractor": ["solidity-comments-extractor@0.0.8", "", {}, "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g=="], + + "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "src-location": ["src-location@1.1.0", "", {}, "sha512-idBVZgLZGzB3B2Et317AFDQto7yRgp1tOuFd+VKIH2dw1jO1b6p07zNjtQoVhkW+CY6oGTp9Y5UIfbJoZRsoFQ=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "terser": ["terser@5.50.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w=="], + + "tevm": ["tevm@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/address": "^1.0.0-rc.151", "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/bun-plugin": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/client-types": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/compiler": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/esbuild-plugin": "^1.0.0-rc.151", "@tevm/evm": "^1.0.0-rc.151", "@tevm/http-client": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/rollup-plugin": "^1.0.0-rc.151", "@tevm/rspack-plugin": "^1.0.0-rc.151", "@tevm/server": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/ts-plugin": "^1.0.0-rc.151", "@tevm/tx": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/viem": "^1.0.0-rc.151", "@tevm/vite-plugin": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151", "@tevm/webpack-plugin": "^1.0.0-rc.151", "commander": "^14.0.3", "effect": "3.21.2", "glob": "^13.0.6", "solc": "^0.8.35" }, "peerDependencies": { "viem": "^2.49.3" }, "bin": { "tevm": "cli.js" } }, "sha512-LyFVsmOZUo4ZnFlZa4zgchK1Z2bHiXyihTlh3Ozin6IbgZa2ReJgdDz2I+UI+27IXzrRKGpMLy1WNAWbhaVSJA=="], + + "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tinyrainbow": ["tinyrainbow@3.1.1", "", {}, "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw=="], + + "tmp": ["tmp@0.2.7", "", {}, "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsx": ["tsx@4.23.12", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], + + "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], + + "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "viem": ["viem@2.55.19", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.34", "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-4QPIX0eYPLsOBk53NKswVMkQoxuP7GlOBnB4wM6dkDokREO4QENNc3bmyPKK1PBTViXh0TPJCHLjIuU20Qi3fg=="], + + "vite": ["vite@8.2.2", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.26", "rolldown": "~1.2.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0 || ^0.5.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q=="], + + "vitest": ["vitest@4.1.11", "", { "dependencies": { "@vitest/expect": "4.1.11", "@vitest/mocker": "4.1.11", "@vitest/pretty-format": "4.1.11", "@vitest/runner": "4.1.11", "@vitest/snapshot": "4.1.11", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.11", "@vitest/browser-preview": "4.1.11", "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-istanbul": "4.1.11", "@vitest/coverage-v8": "4.1.11", "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw=="], + + "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], + + "web3-errors": ["web3-errors@1.3.1", "", { "dependencies": { "web3-types": "^1.10.0" } }, "sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ=="], + + "web3-eth-abi": ["web3-eth-abi@4.4.1", "", { "dependencies": { "abitype": "0.7.1", "web3-errors": "^1.3.1", "web3-types": "^1.10.0", "web3-utils": "^4.3.3", "web3-validator": "^2.0.6" } }, "sha512-60ecEkF6kQ9zAfbTY04Nc9q4eEYM0++BySpGi8wZ2PD1tw/c0SDvsKhV6IKURxLJhsDlb08dATc3iD6IbtWJmg=="], + + "web3-types": ["web3-types@1.10.0", "", {}, "sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw=="], + + "web3-utils": ["web3-utils@4.3.3", "", { "dependencies": { "ethereum-cryptography": "^2.0.0", "eventemitter3": "^5.0.1", "web3-errors": "^1.3.1", "web3-types": "^1.10.0", "web3-validator": "^2.0.6" } }, "sha512-kZUeCwaQm+RNc2Bf1V3BYbF29lQQKz28L0y+FA4G0lS8IxtJVGi5SeDTUkpwqqkdHHC7JcapPDnyyzJ1lfWlOw=="], + + "web3-validator": ["web3-validator@2.0.6", "", { "dependencies": { "ethereum-cryptography": "^2.0.0", "util": "^0.12.5", "web3-errors": "^1.2.0", "web3-types": "^1.6.0", "zod": "^3.21.4" } }, "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg=="], + + "webpack": ["webpack@5.109.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.24.4", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "mime-db": "^1.54.0", "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "watchpack": "^2.5.2", "webpack-sources": "^3.5.1" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw=="], + + "webpack-sources": ["webpack-sources@3.5.1", "", {}, "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + + "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "yoctocolors": ["yoctocolors@2.2.0", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="], + + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@ethereumjs/binarytree/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/binarytree/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], + + "@ethereumjs/mpt/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/mpt/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], + + "@ethereumjs/statemanager/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/statemanager/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], + + "@ethereumjs/trie/@ethereumjs/util": ["@ethereumjs/util@9.1.0", "", { "dependencies": { "@ethereumjs/rlp": "^5.0.2", "ethereum-cryptography": "^2.2.1" } }, "sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog=="], + + "@ethereumjs/trie/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], + + "@ethereumjs/trie/lru-cache": ["lru-cache@10.1.0", "", {}, "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag=="], + + "@ethereumjs/tx/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@ethereumjs/util/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], + + "@latticexyz/common/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/protocol-parser/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/schema-type/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/store/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@latticexyz/world/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], + + "@scure/bip32/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "@scure/bip32/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@scure/bip32/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], + + "@scure/bip39/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], + + "@tevm/base-bundler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/bundler-cache/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/compiler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/config/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/resolutions/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@tevm/tsupconfig/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], + + "@zoltar/ui-core-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-statoblast/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-statoblast/@zoltar/ui-core-shared": ["@zoltar/ui-core-shared@file:../coreShared", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "@zoltar/ui-statoblast/@zoltar/ui-zoltar": ["@zoltar/ui-zoltar@file:../zoltar", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "@zoltar/ui-core-shared": "file:../coreShared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "@zoltar/ui-zoltar/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-zoltar/@zoltar/ui-core-shared": ["@zoltar/ui-core-shared@file:../coreShared", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "ethereum-cryptography/@noble/curves": ["@noble/curves@1.9.0", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg=="], + + "ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "ox/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "prettier-plugin-solidity/@solidity-parser/parser": ["@solidity-parser/parser@0.17.0", "", {}, "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw=="], + + "prettier-plugin-solidity/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "solc-typed-ast/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "solc-typed-ast/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "solc-typed-ast/solc": ["solc@0.8.25", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-7P0TF8gPeudl1Ko3RGkyY6XVCxe2SdD/qQhtns1vl3yAbK/PDifKDLHGtx1t7mX3LgR7ojV7Fg/Kc6Q9D2T8UQ=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], + + "viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "viem/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "viem/abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], + + "web3-eth-abi/abitype": ["abitype@0.7.1", "", { "peerDependencies": { "typescript": ">=4.9.4", "zod": "^3 >=3.19.1" }, "optionalPeers": ["zod"] }, "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ=="], + + "web3-utils/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], + + "web3-validator/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], + + "web3-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@ethereumjs/trie/ethereum-cryptography/@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="], + + "@ethereumjs/trie/ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="], + + "@tevm/base-bundler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/bundler-cache/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/compiler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/config/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/resolutions/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tevm/tsupconfig/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@zoltar/ui-statoblast/@zoltar/ui-core-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-statoblast/@zoltar/ui-zoltar/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "@zoltar/ui-statoblast/@zoltar/ui-zoltar/@zoltar/ui-core-shared": ["@zoltar/ui-core-shared@file:../coreShared", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], + + "@zoltar/ui-zoltar/@zoltar/ui-core-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "solc-typed-ast/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "solc-typed-ast/solc/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "web3-utils/ethereum-cryptography/@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="], + + "web3-utils/ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + + "web3-utils/ethereum-cryptography/@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="], + + "web3-utils/ethereum-cryptography/@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="], + + "web3-validator/ethereum-cryptography/@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="], + + "web3-validator/ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + + "web3-validator/ethereum-cryptography/@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="], + + "web3-validator/ethereum-cryptography/@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "@ethereumjs/trie/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "@zoltar/ui-statoblast/@zoltar/ui-zoltar/@zoltar/ui-core-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + + "web3-utils/ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "web3-utils/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "web3-validator/ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + + "web3-validator/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], + } +} diff --git a/trading/compose.yaml b/ui/trading/compose.yaml similarity index 50% rename from trading/compose.yaml rename to ui/trading/compose.yaml index d8bc5b8dd..45ea26cc5 100644 --- a/trading/compose.yaml +++ b/ui/trading/compose.yaml @@ -3,11 +3,14 @@ name: zoltar-trading services: trading: build: - context: .. - dockerfile: trading/Dockerfile + context: ../.. + dockerfile: ui/Dockerfile + target: local-runtime image: zoltar-trading ports: - - 127.0.0.1:4163:4163 + - 127.0.0.1:4163:8080 + environment: + UI_APP: trading networks: default: diff --git a/trading/ui/css/app.css b/ui/trading/css/app.css similarity index 97% rename from trading/ui/css/app.css rename to ui/trading/css/app.css index 4fc5ba937..fed8abf93 100644 --- a/trading/ui/css/app.css +++ b/ui/trading/css/app.css @@ -68,30 +68,6 @@ summary:focus-visible { .skip-link:focus { transform: translateY(0); } -.demo-banner, -.runtime-banner { - min-height: 34px; - display: flex; - align-items: center; - justify-content: center; - gap: 0.65rem; - border-bottom: 1px solid #48541f; - background: #20280e; - color: #dff797; - font: - 11px / 1.2 ui-monospace, - monospace; - letter-spacing: 0.04em; -} -.runtime-banner { - border-color: #59461f; - background: #2b2211; - color: #f2d99d; -} -.demo-banner strong, -.runtime-banner strong { - letter-spacing: 0.12em; -} .site-chrome { position: sticky; top: 0; @@ -466,9 +442,17 @@ nav a[aria-current="page"] { align-items: flex-start; justify-content: space-between; gap: 2rem; + padding: 0; + border: 0; + border-radius: 0; margin-bottom: 2rem; + background: transparent; + box-shadow: none; } -.route-header h1 { +.route-header > .route-header-main { + width: 100%; +} +.route-header h2 { max-width: 820px; margin: 0.35rem 0 0.55rem; font: @@ -812,13 +796,18 @@ nav a[aria-current="page"] { border: 1px solid #53642b; background: var(--accent-dark); } -.trade-summary > div { +.trade-summary:not(.trade-summary--review) > div { display: grid; gap: 0.3rem; } -.trade-summary > div:last-child { +.trade-summary:not(.trade-summary--review) > div:last-child { text-align: right; } +.trade-summary--review { + grid-template-columns: minmax(0, 1fr); + align-items: stretch; + gap: 0.75rem; +} .trade-summary span, .trade-summary small { color: var(--muted); @@ -1252,8 +1241,7 @@ summary { .fork-question-summary span, .fork-question-summary small, .fork-target-count, -.fork-deployed-targets > span, -.fork-scalar-preview > span { +.fork-deployed-targets > span { color: var(--muted); font: 10px ui-monospace, @@ -1315,6 +1303,13 @@ summary { .scalar-fork-picker .secondary-action { min-width: 160px; } +.scalar-fork-picker > .market-scalar-deploy { + grid-column: 1 / -1; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; +} .fork-invalid-option { display: flex; align-items: center; @@ -1323,19 +1318,6 @@ summary { color: var(--muted); font-size: 12px; } -.fork-scalar-preview { - display: grid; - gap: 0.25rem; -} -.fork-scalar-preview strong { - color: var(--accent); - font: - 16px ui-monospace, - monospace; -} -.fork-scalar-preview small { - color: var(--muted); -} .fork-deployed-targets { display: flex; grid-column: 1 / -1; @@ -1549,13 +1531,13 @@ summary { .deployment-setup__advanced .deployment-setup__fields { grid-template-columns: 1fr; } - details:not(.wallet-summary) > summary::after { + details:not(.wallet-summary):not(.simulation-banner-details) > summary::after { margin-left: 0.45rem; content: "▾"; color: var(--muted); font-size: 10px; } - details:not(.wallet-summary)[open] > summary::after { + details:not(.wallet-summary):not(.simulation-banner-details)[open] > summary::after { content: "▴"; } .site-header nav { @@ -1647,13 +1629,6 @@ summary { isolation: isolate; background: #090d12; } - .demo-banner, - .runtime-banner { - align-items: flex-start; - flex-direction: column; - gap: 0.15rem; - padding: 0.45rem 1rem; - } .site-header { grid-template-columns: auto 1fr; padding: 0.75rem 1rem 0; @@ -1801,7 +1776,7 @@ summary { display: grid; gap: 1rem; } - .route-header h1 { + .route-header h2 { font-size: 2rem; } .detail-grid { diff --git a/ui/trading/index.html b/ui/trading/index.html new file mode 100644 index 000000000..11be41ff1 --- /dev/null +++ b/ui/trading/index.html @@ -0,0 +1,123 @@ + + + + + + Statoblast trading + + + + + + + +
Loading...
+ + + + + diff --git a/ui/trading/package.json b/ui/trading/package.json new file mode 100644 index 000000000..9acef1c96 --- /dev/null +++ b/ui/trading/package.json @@ -0,0 +1,48 @@ +{ + "description": "Statoblast conditional prediction-market trading interface", + "name": "@zoltar/ui-trading", + "private": true, + "repository": {}, + "license": "Unlicense", + "type": "module", + "devDependencies": { + "better-typescript-lib": "2.11.0", + "bun-types": "1.3.11", + "typescript": "5.8.2" + }, + "dependencies": { + "@preact/signals": "2.0.1", + "@zoltar/shared": "file:../../shared", + "@zoltar/ui-core-shared": "file:../coreShared", + "@zoltar/ui-statoblast": "file:../statoblast", + "@zoltar/ui-zoltar": "file:../zoltar", + "preact": "10.26.4", + "source-map": "0.6.1", + "tevm": "1.0.0-rc.151" + }, + "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", + "brace-expansion": "5.0.9", + "fast-uri": "3.1.5", + "nanoid": "3.3.18", + "postcss": "8.5.23", + "tmp": "0.2.7", + "ws": "8.21.0" + }, + "scripts": { + "generate": "cd ../.. && bun run ensure-contract-artifacts && cd ui/trading && bun ../coreShared/build/vendor.mts trading", + "vendor": "bun ../coreShared/build/vendor.mts trading", + "build:workers": "bun ../coreShared/build/workers.mts trading", + "build:tests": "bun ../coreShared/build/tests.mts trading", + "test": "bun test --isolate ./ts/tests", + "build": "bun run generate && bun x tsc --project tsconfig.json && bun run build:workers", + "build:prod": "bun run generate && bun ../coreShared/build/production.mts trading", + "watch": "bun ../coreShared/build/watch.mts trading", + "serve": "bun ../coreShared/dev-server.ts trading", + "qa:browser": "bun ./scripts/browser-qa.mts", + "docker:build": "docker build --file ../Dockerfile --target local-runtime --tag zoltar-trading ../..", + "docker:run": "docker run --rm --env UI_APP=trading --publish 4163:8080 zoltar-trading" + } +} diff --git a/ui/trading/scripts/browser-qa.mts b/ui/trading/scripts/browser-qa.mts new file mode 100644 index 000000000..82ea54e3f --- /dev/null +++ b/ui/trading/scripts/browser-qa.mts @@ -0,0 +1,266 @@ +import { promises as fs } from 'node:fs' + +type CdpMessage = Readonly<{ id?: number; method?: string; params?: Record; result?: Record; error?: unknown; sessionId?: string }> + +const outputDirectory = process.env.TRADING_QA_OUTPUT ?? '/tmp/zoltar-trading-qa' +const baseUrl = process.env.TRADING_QA_URL ?? 'http://127.0.0.1:4163' +const selectedNames = new Set((process.env.TRADING_QA_SCENARIOS ?? '').split(',').filter(name => name !== '')) +const injectedFailure = process.env.TRADING_QA_INJECT_FAILURE +const debuggingPort = 9227 +const simulationPath = '/?simulate=1&simScenario=trading' +await fs.mkdir(outputDirectory, { recursive: true }) +const userDataDirectory = await fs.mkdtemp('/tmp/zoltar-trading-qa-browser-') +const browser = Bun.spawn({ + cmd: ['chromium', '--headless=new', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--disable-background-networking', '--disable-component-update', '--no-first-run', `--remote-debugging-port=${debuggingPort}`, `--user-data-dir=${userDataDirectory}`, 'about:blank'], + stdout: 'ignore', + stderr: 'ignore', +}) + +async function waitForDebugger() { + for (let attempt = 0; attempt < 100; attempt++) { + try { + const response = await fetch(`http://127.0.0.1:${debuggingPort}/json/version`) + if (response.ok) return + } catch (error) { + if (typeof error !== 'object' || error === null || !('code' in error) || !['ConnectionRefused', 'ECONNREFUSED', 'ECONNRESET'].includes(String(error.code))) throw error + } + await Bun.sleep(100) + } + throw new Error('Chromium debugging endpoint did not start') +} + +await waitForDebugger() +const targetResponse = await fetch(`http://127.0.0.1:${debuggingPort}/json/new?about:blank`, { method: 'PUT' }) +const target: unknown = await targetResponse.json() +if (typeof target !== 'object' || target === null || !('webSocketDebuggerUrl' in target) || typeof target.webSocketDebuggerUrl !== 'string') throw new Error('Chromium target did not expose a debugger WebSocket') + +const socket = new WebSocket(target.webSocketDebuggerUrl) +await new Promise((resolve, reject) => { + socket.addEventListener('open', () => resolve(), { once: true }) + socket.addEventListener('error', () => reject(new Error('Debugger WebSocket failed')), { once: true }) +}) +let nextId = 1 +const pending = new Map): void; reject(reason: unknown): void }>() +const runtimeErrors: string[] = [] +const failedRequests: string[] = [] +socket.addEventListener('message', event => { + const candidate: unknown = JSON.parse(String(event.data)) + if (typeof candidate !== 'object' || candidate === null) { + runtimeErrors.push('Chromium returned a malformed debugger message') + return + } + const message: CdpMessage = candidate + if (message.id !== undefined) { + const request = pending.get(message.id) + if (request === undefined) return + pending.delete(message.id) + if (message.error !== undefined) request.reject(message.error) + else request.resolve(message.result ?? {}) + return + } + if (message.method === 'Runtime.exceptionThrown') runtimeErrors.push(JSON.stringify(message.params ?? {})) + if (message.method === 'Runtime.consoleAPICalled' && message.params?.type === 'error') runtimeErrors.push(JSON.stringify(message.params)) + if (message.method === 'Log.entryAdded') { + const entry = message.params?.entry + if (typeof entry === 'object' && entry !== null && 'level' in entry && entry.level === 'error') runtimeErrors.push(JSON.stringify(entry)) + } + if (message.method === 'Network.loadingFailed') failedRequests.push(JSON.stringify(message.params ?? {})) + if (message.method === 'Network.responseReceived') { + const response = message.params?.response + if (typeof response === 'object' && response !== null && 'status' in response && typeof response.status === 'number' && response.status >= 400) failedRequests.push(JSON.stringify(response)) + } + if (message.method === 'Target.attachedToTarget') { + const sessionId = message.params?.sessionId + const targetInfo = message.params?.targetInfo + if (typeof sessionId !== 'string') return + if (typeof targetInfo !== 'object' || targetInfo === null || !('type' in targetInfo) || !['worker', 'shared_worker', 'service_worker'].includes(String(targetInfo.type))) { + void command('Runtime.runIfWaitingForDebugger', {}, sessionId) + return + } + void attachWorkerSession(sessionId).catch(error => runtimeErrors.push(`Could not attach QA collection to worker: ${String(error)}`)) + } +}) + +function command(method: string, params: Record = {}, sessionId?: string) { + const id = nextId++ + return new Promise>((resolve, reject) => { + pending.set(id, { resolve, reject }) + socket.send(JSON.stringify({ id, method, params, ...(sessionId === undefined ? {} : { sessionId }) })) + }) +} + +async function attachWorkerSession(sessionId: string) { + await Promise.all([command('Runtime.enable', {}, sessionId), command('Log.enable', {}, sessionId), command('Network.enable', {}, sessionId)]) + await command('Runtime.runIfWaitingForDebugger', {}, sessionId) +} + +await command('Page.enable') +await command('Runtime.enable') +await command('Log.enable') +await command('Network.enable') +await command('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }) + +const waitForSeededMarket = `(async () => { + for (let attempt = 0; attempt < 600; attempt++) { + const market = document.querySelector('.live-market-button') + const simulation = document.querySelector('.simulation-banner-details') + if (market instanceof HTMLButtonElement && simulation !== null) return true + await new Promise(resolve => setTimeout(resolve, 100)) + } + throw new Error('The Trading TEVM scenario did not expose its seeded SecurityPool') +})()` +const selectSeededMarket = `(async () => { + await (${waitForSeededMarket}) + const market = document.querySelector('.live-market-button') + if (!(market instanceof HTMLButtonElement)) return false + market.click() + for (let attempt = 0; attempt < 100; attempt++) { + if (document.querySelector('.two-column:not(.two-column--single)') !== null) return true + await new Promise(resolve => setTimeout(resolve, 100)) + } + return false +})()` +const commonAssertion = `document.querySelector('.demo-banner') === null && !document.body.textContent?.includes('SIMULATED DATA') && !document.body.textContent?.includes('Demo mode') && !document.body.textContent?.includes('Loading...') && document.querySelector('.simulation-banner-details') !== null && document.querySelector('.brand')?.textContent?.includes('Statoblast trading') === true && document.querySelector('.network-pill')?.textContent?.includes('Deployment verified') === true && document.documentElement.scrollWidth <= document.documentElement.clientWidth` + +const scenarios = [ + ...( + [ + ['simulation-markets-desktop', 1440, 900], + ['simulation-markets-mobile', 390, 844], + ] as const + ).map(([name, width, height]) => ({ + name, + width, + height, + path: `${simulationPath}#/markets`, + assertExpression: `(async () => { await (${waitForSeededMarket}); return ${commonAssertion} && document.title === 'Markets · Statoblast trading' && document.querySelectorAll('.live-market-button').length > 0 })()`, + })), + ...( + [ + ['simulation-market-detail-desktop', 1440, 900], + ['simulation-market-detail-mobile', 390, 844], + ] as const + ).map(([name, width, height]) => ({ + name, + width, + height, + path: `${simulationPath}#/markets`, + evaluate: selectSeededMarket, + assertExpression: `(${commonAssertion}) && document.querySelector('.two-column:not(.two-column--single)') !== null && document.querySelector('.section .fact-list') !== null`, + })), + ...( + [ + ['simulation-liquidity-desktop', 1440, 900], + ['simulation-liquidity-mobile', 390, 844], + ] as const + ).map(([name, width, height]) => ({ + name, + width, + height, + path: `${simulationPath}#/markets`, + evaluate: `(async () => { if (!(await (${selectSeededMarket}))) return false; const link = document.querySelector('a[href="#/liquidity"]'); if (!(link instanceof HTMLAnchorElement)) return false; link.click(); for (let attempt = 0; attempt < 100; attempt++) { if (location.hash === '#/liquidity' && [...document.querySelectorAll('.operation-block h3')].some(heading => heading.textContent === 'Live liquidity')) return true; await new Promise(resolve => setTimeout(resolve, 100)); } return false })()`, + assertExpression: `(${commonAssertion}) && location.hash === '#/liquidity' && [...document.querySelectorAll('.operation-block h3')].some(heading => heading.textContent === 'Live liquidity')`, + })), + { + name: 'simulation-scenario-navigation-desktop', + width: 1440, + height: 900, + path: `${simulationPath}#/markets`, + evaluate: `(async () => { + await (${waitForSeededMarket}) + const details = document.querySelector('.simulation-banner-details') + if (!(details instanceof HTMLDetailsElement)) throw new Error('Simulation details are unavailable') + details.open = true + const select = document.querySelector('.simulation-control-select') + if (!(select instanceof HTMLSelectElement)) throw new Error('Simulation scenario selector is unavailable') + select.value = 'scenario:baseline' + select.dispatchEvent(new Event('change', { bubbles: true })) + for (let attempt = 0; attempt < 600; attempt++) { + const currentSelect = document.querySelector('.simulation-control-select') + if (location.hash.includes('simScenario=baseline') && document.querySelector('.simulation-banner-compact-summary')?.textContent?.includes('Baseline') === true && currentSelect instanceof HTMLSelectElement && !currentSelect.disabled) break + await new Promise(resolve => setTimeout(resolve, 100)) + } + const updatedSelect = document.querySelector('.simulation-control-select') + if (!(updatedSelect instanceof HTMLSelectElement)) throw new Error('Simulation scenario selector disappeared after selecting Baseline') + if (updatedSelect.value !== 'scenario:baseline') throw new Error('Trading did not activate the Baseline environment') + history.back() + for (let attempt = 0; attempt < 600; attempt++) { + const currentSelect = document.querySelector('.simulation-control-select') + if (location.href.includes('simScenario=trading') && currentSelect instanceof HTMLSelectElement && currentSelect.value === 'scenario:trading' && document.querySelector('.live-market-button') !== null) break + await new Promise(resolve => setTimeout(resolve, 100)) + } + if (!location.href.includes('simScenario=trading') || document.querySelector('.simulation-control-select')?.value !== 'scenario:trading' || document.querySelector('.live-market-button') === null) throw new Error('Browser Back did not restore the seeded Trading environment: ' + location.href + ' / ' + document.querySelector('.simulation-control-select')?.value + ' / ' + document.querySelector('.error-notice')?.textContent) + history.forward() + for (let attempt = 0; attempt < 600; attempt++) { + const currentSelect = document.querySelector('.simulation-control-select') + if (location.hash.includes('simScenario=baseline') && currentSelect instanceof HTMLSelectElement && currentSelect.value === 'scenario:baseline' && !currentSelect.disabled) break + await new Promise(resolve => setTimeout(resolve, 100)) + } + if (!location.hash.includes('simScenario=baseline') || document.querySelector('.simulation-control-select')?.value !== 'scenario:baseline') throw new Error('Browser Forward did not restore the Baseline environment') + const liquidityLink = document.querySelector('a[href^="#/liquidity?"]') + if (!(liquidityLink instanceof HTMLAnchorElement)) throw new Error('Liquidity navigation did not preserve the Baseline scenario') + liquidityLink.click() + for (let attempt = 0; attempt < 100; attempt++) { + if (location.hash.startsWith('#/liquidity?') && location.hash.includes('simScenario=baseline') && document.querySelector('a[aria-current="page"]')?.textContent === 'Liquidity') break + await new Promise(resolve => setTimeout(resolve, 100)) + } + if (!location.hash.startsWith('#/liquidity?') || document.body.textContent?.includes('Page not found')) throw new Error('Liquidity navigation lost the Baseline scenario') + history.back() + for (let attempt = 0; attempt < 100; attempt++) { + if (location.hash.startsWith('#/markets?') && location.hash.includes('simScenario=baseline') && document.querySelector('a[aria-current="page"]')?.textContent === 'Markets') break + await new Promise(resolve => setTimeout(resolve, 100)) + } + const restoredSelect = document.querySelector('.simulation-control-select') + if (!(restoredSelect instanceof HTMLSelectElement) || restoredSelect.value !== 'scenario:baseline') throw new Error('Browser Back did not restore the Baseline markets route') + restoredSelect.value = 'scenario:trading' + restoredSelect.dispatchEvent(new Event('change', { bubbles: true })) + for (let attempt = 0; attempt < 600; attempt++) { + if (location.hash.includes('simScenario=trading') && document.querySelector('.live-market-button') !== null) return true + await new Promise(resolve => setTimeout(resolve, 100)) + } + throw new Error('Trading did not restore its seeded environment after scenario navigation: ' + location.hash + ' / ' + document.querySelector('.simulation-control-select')?.value + ' / ' + document.querySelector('.error-notice')?.textContent) + })()`, + assertExpression: `(${commonAssertion}) && location.hash.includes('simScenario=trading') && document.querySelector('.live-market-button') !== null`, + }, + { + name: 'simulation-scenario-direct-reload-desktop', + width: 1440, + height: 900, + path: '/#/markets?simulate=1&simScenario=trading', + assertExpression: `(async () => { await (${waitForSeededMarket}); return ${commonAssertion} && location.hash === '#/markets?simulate=1&simScenario=trading' && document.querySelector('a[aria-current="page"]')?.textContent === 'Markets' && !document.body.textContent?.includes('Page not found') })()`, + }, +] as const + +const selectedScenarios = selectedNames.size === 0 ? scenarios : scenarios.filter(scenario => selectedNames.has(scenario.name)) +if (selectedScenarios.length === 0) throw new Error('TRADING_QA_SCENARIOS did not match a browser scenario') + +try { + for (const scenario of selectedScenarios) { + await command('Emulation.setDeviceMetricsOverride', { width: scenario.width, height: scenario.height, deviceScaleFactor: 1, mobile: false }) + await command('Page.navigate', { url: `${baseUrl}${scenario.path}` }) + await Bun.sleep(600) + if ('evaluate' in scenario) { + const evaluated = await command('Runtime.evaluate', { expression: scenario.evaluate, returnByValue: true, awaitPromise: true }) + const result = evaluated.result + if (typeof result !== 'object' || result === null || !('value' in result) || result.value !== true) throw new Error(`Setup expression failed for ${scenario.name}: ${JSON.stringify(evaluated)}`) + } + const evaluated = await command('Runtime.evaluate', { expression: scenario.assertExpression, returnByValue: true, awaitPromise: true }) + const result = evaluated.result + if (typeof result !== 'object' || result === null || !('value' in result) || result.value !== true) throw new Error(`Browser assertion failed for ${scenario.name}: ${JSON.stringify(evaluated)}`) + if (injectedFailure === 'page-console') await command('Runtime.evaluate', { expression: `console.error('injected Trading QA page failure')` }) + if (injectedFailure === 'worker-runtime') await command('Runtime.evaluate', { expression: `new Worker(URL.createObjectURL(new Blob(["console.error('injected Trading QA worker failure'); throw new Error('injected Trading QA worker failure')"], { type: 'text/javascript' })))` }) + if (injectedFailure !== undefined) await Bun.sleep(500) + const screenshot = await command('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false }) + if (typeof screenshot.data !== 'string') throw new Error(`Screenshot data missing for ${scenario.name}`) + await fs.writeFile(`${outputDirectory}/${scenario.name}.png`, Buffer.from(screenshot.data, 'base64')) + console.log(`${scenario.name}: ${scenario.width.toString()}x${scenario.height.toString()} ${scenario.path}`) + } + console.log(`Runtime errors: ${runtimeErrors.length.toString()}`) + console.log(`Failed requests: ${failedRequests.length.toString()}`) + if (runtimeErrors.length > 0) throw new Error(`Browser QA observed ${runtimeErrors.length.toString()} runtime errors`) + if (failedRequests.length > 0) throw new Error(`Browser QA observed ${failedRequests.length.toString()} failed requests`) +} finally { + socket.close() + browser.kill() + await fs.rm(userDataDirectory, { recursive: true, force: true }) +} diff --git a/trading/start.bat b/ui/trading/start.bat similarity index 100% rename from trading/start.bat rename to ui/trading/start.bat diff --git a/ui/trading/ts/app/App.tsx b/ui/trading/ts/app/App.tsx new file mode 100644 index 000000000..a082588de --- /dev/null +++ b/ui/trading/ts/app/App.tsx @@ -0,0 +1,458 @@ +import { useCallback, useEffect, useRef, useState } from 'preact/hooks' +import type { PublicClient } from '@zoltar/shared/ethereum' +import { Help } from '../features/Help.js' +import { LiveTrading, type WalletSummaryState } from '../features/LiveTrading.js' +import { TradingDeploymentSetup, type DeploymentWalletState, type TradingDeploymentSetupServices } from '../features/TradingDeploymentSetup.js' +import type { DeploymentConfiguration } from '../protocol/config.js' +import { loadCoreDeployments } from '../protocol/coreDeployments.js' +import { deploymentConfigurationForPlan, getTradingDeploymentPlan, loadTradingDeploymentStatus, type CoreDeployment } from '../protocol/deployment.js' +import { createTradingPublicClient, publicErrorMessage, validateRpcChainId } from '../protocol/live.js' +import { formatUnits, shortAddress } from '../lib/format.js' +import { getActiveSimulationController } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import * as commonCopy from '../copy/common.js' +import * as appCopy from '../copy/app.js' +import { AppHeaderShell } from '@zoltar/ui-core-shared/app/components/AppHeaderShell.js' +import { AppPageHeading } from '@zoltar/ui-core-shared/app/components/AppPageHeading.js' +import { RouteHeader } from '@zoltar/ui-core-shared/components/RouteHeader.js' +import { initializeTradingActiveEnvironment } from './activeEnvironment.js' +import { getTradingEnvironmentLocationKey, getTradingRouteHref, getTradingRoutePath } from '../lib/routing.js' + +const tradingRoutes = ['markets', 'market', 'liquidity', 'portfolio', 'deploy', 'help'] as const +type TradingRoute = (typeof tradingRoutes)[number] | `security-pool/${string}` | 'not-found' + +export function currentRoute(): TradingRoute { + const route = getTradingRoutePath(window.location.hash).replace(/^#\/?/, '') || 'markets' + if (route === 'developer') return 'markets' + return tradingRoutes.find(candidate => candidate === route) ?? (/^security-pool\/0x[0-9a-f]{40}$/i.test(route) ? `security-pool/${route.slice('security-pool/'.length)}` : 'not-found') +} + +export function tradingDocumentTitle(route: TradingRoute) { + let label = `${route.charAt(0).toUpperCase()}${route.slice(1)}` + if (route === 'not-found') label = appCopy.notFound + if (route === 'market') label = appCopy.market + if (route.startsWith('security-pool/')) label = appCopy.securityPool + return appCopy.documentTitle(label) +} + +function tradingPageTitle(route: TradingRoute) { + return tradingDocumentTitle(route).replace(` · ${appCopy.appName}`, '') +} + +function renderNotFoundRoute() { + return ( +
+ + + {appCopy.returnToMarkets} + +
+ ) +} + +type LiveDeploymentStatus = 'loading' | 'verified' | 'unavailable' + +async function withDeploymentLoadTimeout(label: string, operation: Promise, milliseconds = 15_000): Promise { + let timeout: ReturnType | undefined + try { + return await Promise.race([ + operation, + new Promise((_resolve, reject) => { + timeout = setTimeout(() => reject(new Error(`${label} timed out`)), milliseconds) + }), + ]) + } finally { + if (timeout !== undefined) clearTimeout(timeout) + } +} + +export async function resolveCanonicalLiveDeployment(coreDeployments: readonly CoreDeployment[], createPublicClient: (configuration: DeploymentConfiguration) => PublicClient = createTradingPublicClient) { + const core = coreDeployments[0] + if (core === undefined) throw new Error('No canonical network deployment is available') + const plan = getTradingDeploymentPlan(core, 30) + const configuration = deploymentConfigurationForPlan(plan, core.defaultRpcUrl) + const client = createPublicClient(configuration) + validateRpcChainId(await withDeploymentLoadTimeout('Trading RPC chain verification', client.getChainId()), core.chainId) + const status = await withDeploymentLoadTimeout('Trading deployment verification', loadTradingDeploymentStatus(client, plan)) + if (!status.factory || !status.router) throw new Error('Trading contracts have not been deployed') + return configuration +} + +async function resolveLiveDeployment() { + return await resolveCanonicalLiveDeployment(await loadCoreDeployments()) +} + +export function tradingNetworkLabel(liveDeploymentStatus: LiveDeploymentStatus) { + if (liveDeploymentStatus === 'verified') return commonCopy.deploymentVerified + if (liveDeploymentStatus === 'unavailable') return 'Deployment unavailable' + return 'Checking deployment' +} + +function networkToneClass(liveDeploymentStatus: LiveDeploymentStatus) { + if (liveDeploymentStatus === 'unavailable') return ' network-pill--warn' + if (liveDeploymentStatus !== 'verified') return ' network-pill--neutral' + return '' +} + +export type UniverseOption = Readonly<{ id: string; label: string; accessibleLabel?: string }> + +export function UniverseSelector({ options, selectedId, disabled, onChange }: { options: readonly UniverseOption[]; selectedId: string | undefined; disabled: boolean; onChange(selectedId: string): void }) { + const selected = options.find(option => option.id === selectedId) + return ( + + ) +} + +export function WalletSummary({ summary, onRetry }: { summary: WalletSummaryState; onRetry?(): void }) { + if (summary.account === undefined) return null + let ethDisplay = '…' + let repBalance = '…' + if (summary.status === 'error') { + ethDisplay = '—' + repBalance = '—' + } else if (summary.status === 'ready') { + if (summary.ethAttoEth !== undefined) ethDisplay = formatUnits(summary.ethAttoEth, 18, 18) + if (summary.repAttoRep !== undefined) repBalance = formatUnits(summary.repAttoRep, 18, 18) + } + return ( +
+ + {summary.account} + {shortAddress(summary.account)} + Loading balances… +
+ + ETH + {ethDisplay} + + + REP + {repBalance} + +
+
+
+
+ Connected account + {summary.account} +
+
+ + ETH + {ethDisplay} + + + REP + {repBalance} + +
+ {summary.status === 'error' ? ( + + + {summary.errorLabel ?? 'Balances unavailable'} + + {onRetry === undefined ? null : ( + + )} + + ) : null} +
+ {summary.status === 'loading' ? ( + + Loading wallet ETH and current-universe REP balances + + ) : null} +
+ ) +} + +export function walletSummaryForUniverse(summary: WalletSummaryState, selectedUniverseId: string | undefined): WalletSummaryState { + if (summary.universeId === selectedUniverseId) return summary + return { account: summary.account, ethAttoEth: undefined, repAttoRep: undefined, status: summary.account === undefined ? 'disconnected' : 'loading', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } +} + +export function routeOwnsLiveWallet(route: string) { + return route !== 'deploy' && route !== 'help' && route !== 'not-found' +} + +export function walletSummaryAfterRouteChange(summary: WalletSummaryState, previousRoute: string, nextRoute: string, selectedUniverseId: string | undefined): WalletSummaryState { + if (routeOwnsLiveWallet(previousRoute) === routeOwnsLiveWallet(nextRoute)) return summary + return { account: undefined, ethAttoEth: undefined, repAttoRep: undefined, status: 'disconnected', error: undefined, errorLabel: undefined, universeId: selectedUniverseId } +} + +export function compactUniqueUniverseIds(universeIds: readonly string[]) { + if (new Set(universeIds).size !== universeIds.length) throw new Error('Universe IDs must be unique') + let edgeLength = 3 + while (true) { + const labels = universeIds.map(universeId => (universeId.length <= edgeLength * 2 + 1 ? universeId : `${universeId.slice(0, edgeLength)}…${universeId.slice(-edgeLength)}`)) + if (new Set(labels).size === labels.length) return labels + edgeLength++ + } +} + +export function buildLiveUniverseOptions(universeIds: readonly bigint[]): readonly UniverseOption[] { + const ids = universeIds.map(universeId => universeId.toString()) + const compactIds = compactUniqueUniverseIds(ids) + return universeIds.map((universeId, index) => { + const id = ids[index] + const compactId = compactIds[index] + if (id === undefined || compactId === undefined) throw new Error('Universe label generation failed') + return universeId === 0n ? { id, label: 'Genesis universe', accessibleLabel: 'Genesis universe' } : { id, label: `Universe ${compactId}`, accessibleLabel: `Universe ${id}` } + }) +} + +function deploymentWalletLabel(state: DeploymentWalletState) { + if (state.connecting) return 'Connecting wallet…' + if (state.account === undefined) return 'Connect wallet' + return shortAddress(state.account) +} + +export function App({ + deploymentSetupServices, + initializeEnvironment = initializeTradingActiveEnvironment, + loadLiveDeployment = resolveLiveDeployment, +}: { + deploymentSetupServices?: TradingDeploymentSetupServices + initializeEnvironment?: () => Promise + loadLiveDeployment?: () => Promise +} = {}) { + const [route, setRoute] = useState(currentRoute) + const [liveDeploymentStatus, setLiveDeploymentStatus] = useState('loading') + const [liveConfiguration, setLiveConfiguration] = useState() + const [liveConfigurationError, setLiveConfigurationError] = useState() + const [workflowLocked, setWorkflowLocked] = useState(false) + const [selectedUniverseId, setSelectedUniverseId] = useState() + const [liveUniverseOptions, setLiveUniverseOptions] = useState([]) + const [liveWalletSummary, setLiveWalletSummary] = useState({ account: undefined, ethAttoEth: undefined, repAttoRep: undefined, status: 'disconnected', error: undefined, errorLabel: undefined, universeId: undefined }) + const [walletSummaryRetryNonce, setWalletSummaryRetryNonce] = useState(0) + const [walletConnectRequestNonce, setWalletConnectRequestNonce] = useState(0) + const [deploymentWalletRequestNonce, setDeploymentWalletRequestNonce] = useState(0) + const [deploymentWalletState, setDeploymentWalletState] = useState({ account: undefined, connecting: false, ready: false }) + const [deploymentSettingsHost, setDeploymentSettingsHost] = useState() + const [activeEnvironmentNonce, setActiveEnvironmentNonce] = useState(0) + const activeEnvironmentLocationRef = useRef(getTradingEnvironmentLocationKey()) + const routeRef = useRef(route) + const workflowLockedRef = useRef(workflowLocked) + routeRef.current = route + workflowLockedRef.current = workflowLocked + const updateWorkflowLock = useCallback((locked: boolean) => { + workflowLockedRef.current = locked + setWorkflowLocked(locked) + }, []) + const updateLiveUniverses = useCallback((universeIds: readonly bigint[], authoritativeSelection: bigint | undefined) => { + const options = buildLiveUniverseOptions(universeIds) + setLiveUniverseOptions(options) + setSelectedUniverseId(current => { + if (current !== undefined && options.some(option => option.id === current)) return current + return authoritativeSelection?.toString() + }) + }, []) + const showUniverseSelector = route !== 'deploy' && route !== 'help' && liveDeploymentStatus !== 'unavailable' + const walletSummary = walletSummaryForUniverse(liveWalletSummary, selectedUniverseId) + const retryWalletSummary = () => { + setLiveWalletSummary(current => ({ account: current.account, ethAttoEth: undefined, repAttoRep: undefined, status: current.account === undefined ? 'disconnected' : 'loading', error: undefined, errorLabel: undefined, universeId: selectedUniverseId })) + setWalletSummaryRetryNonce(current => current + 1) + } + const completeWalletDeployment = useCallback((configuration: DeploymentConfiguration) => { + setLiveConfiguration(configuration) + setLiveConfigurationError(undefined) + setLiveDeploymentStatus('verified') + }, []) + const updateDeploymentWalletState = useCallback((state: DeploymentWalletState) => setDeploymentWalletState(state), []) + const deploymentSetupActive = route !== 'not-found' && route !== 'help' && (route === 'deploy' || liveDeploymentStatus === 'unavailable') + const displayedRoute = deploymentSetupActive ? 'deploy' : route + const refreshActiveEnvironment = useCallback(async () => { + const previousLocationKey = activeEnvironmentLocationRef.current + const nextLocationKey = getTradingEnvironmentLocationKey() + if (nextLocationKey === previousLocationKey) return + activeEnvironmentLocationRef.current = nextLocationKey + setLiveDeploymentStatus('loading') + setLiveConfiguration(undefined) + setLiveConfigurationError(undefined) + setSelectedUniverseId(undefined) + setLiveUniverseOptions([]) + setLiveWalletSummary({ account: undefined, ethAttoEth: undefined, repAttoRep: undefined, status: 'disconnected', error: undefined, errorLabel: undefined, universeId: undefined }) + try { + await initializeEnvironment() + } catch (error) { + activeEnvironmentLocationRef.current = previousLocationKey + throw error + } + setActiveEnvironmentNonce(current => current + 1) + }, [initializeEnvironment]) + useEffect(() => { + const update = () => { + if (workflowLockedRef.current) { + window.history.replaceState(undefined, '', getTradingRouteHref(`#/${routeRef.current}`)) + return + } + const nextRoute = currentRoute() + setLiveWalletSummary(current => walletSummaryAfterRouteChange(current, routeRef.current, nextRoute, selectedUniverseId)) + routeRef.current = nextRoute + setRoute(nextRoute) + } + window.addEventListener('hashchange', update) + return () => window.removeEventListener('hashchange', update) + }, [selectedUniverseId]) + useEffect(() => { + const synchronizeEnvironment = () => { + queueMicrotask(() => { + if (getTradingEnvironmentLocationKey() === activeEnvironmentLocationRef.current) return + void refreshActiveEnvironment().catch(error => { + setLiveConfiguration(undefined) + setLiveConfigurationError(publicErrorMessage(error, 'Unable to load the trading environment')) + setLiveDeploymentStatus('unavailable') + }) + }) + } + window.addEventListener('popstate', synchronizeEnvironment) + return () => window.removeEventListener('popstate', synchronizeEnvironment) + }, [refreshActiveEnvironment]) + useEffect(() => { + let active = true + setLiveDeploymentStatus('loading') + void (async () => { + try { + const loaded = await loadLiveDeployment() + if (!active) return + setLiveConfiguration(loaded) + setLiveConfigurationError(undefined) + setLiveDeploymentStatus('verified') + } catch (error) { + if (!active) return + setLiveConfiguration(undefined) + setLiveConfigurationError(publicErrorMessage(error, 'Unable to load the trading deployment')) + setLiveDeploymentStatus('unavailable') + } + })() + return () => { + active = false + } + }, [activeEnvironmentNonce, loadLiveDeployment]) + let content + if (route === 'not-found') content = renderNotFoundRoute() + else if (route === 'help') content = + else if (route === 'deploy') + content = ( + + ) + else if (liveDeploymentStatus === 'unavailable') + content = ( + + ) + else + content = ( + + ) + const simulationController = getActiveSimulationController() + return ( +
+ + window.location.reload()} + renderHeader={simulationBanner => ( +
+ {simulationBanner} +
+ event.preventDefault() : undefined}> + {appCopy.brandMark} + + {appCopy.appName} + + + +
+ + + {tradingNetworkLabel(liveDeploymentStatus)} + + {showUniverseSelector ? : null} + {showUniverseSelector ? : null} + {deploymentSetupActive ? ( + + ) : null} + {deploymentSetupActive ?
setDeploymentSettingsHost(element ?? undefined)} /> : null} + {liveDeploymentStatus === 'verified' && routeOwnsLiveWallet(route) ? ( + + ) : null} +
+
+
+ )} + /> + {content} +
+ ) +} diff --git a/ui/trading/ts/app/activeEnvironment.ts b/ui/trading/ts/app/activeEnvironment.ts new file mode 100644 index 000000000..a668946a0 --- /dev/null +++ b/ui/trading/ts/app/activeEnvironment.ts @@ -0,0 +1,11 @@ +import { initializeActiveEnvironment } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' + +export const tradingActiveEnvironmentDependencies = { + appId: 'trading', +} satisfies NonNullable[1]> + +export async function initializeTradingActiveEnvironment(location: Parameters[0] = window.location, options: Parameters[2] = {}) { + const backend = await initializeActiveEnvironment(location, tradingActiveEnvironmentDependencies, options) + await backend.waitUntilReady?.() + return backend +} diff --git a/trading/ui/ts/components/ProbabilityBar.tsx b/ui/trading/ts/components/ProbabilityBar.tsx similarity index 100% rename from trading/ui/ts/components/ProbabilityBar.tsx rename to ui/trading/ts/components/ProbabilityBar.tsx diff --git a/ui/trading/ts/components/Status.tsx b/ui/trading/ts/components/Status.tsx new file mode 100644 index 000000000..bdb8e9cb2 --- /dev/null +++ b/ui/trading/ts/components/Status.tsx @@ -0,0 +1,29 @@ +import { ReadOnlyAddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { getTradingRouteHref } from '../lib/routing.js' + +export function Status({ tone, children }: { tone: 'good' | 'warn' | 'bad' | 'neutral'; children: preact.ComponentChildren }) { + const badgeTones = { bad: 'blocked', good: 'ok', neutral: 'muted', warn: 'warning' } as const + const badgeTone = badgeTones[tone] + return ( + + {children} + + ) +} + +export function AddressValue({ value }: { value: string }) { + return +} + +function securityPoolHref(value: string) { + return getTradingRouteHref(`#/security-pool/${value}`) +} + +export function SecurityPoolAddressLink({ value, disabled = false }: { value: string; disabled?: boolean }) { + return ( + event.preventDefault() : undefined}> + + + ) +} diff --git a/ui/trading/ts/copy/app.ts b/ui/trading/ts/copy/app.ts new file mode 100644 index 000000000..ccc6c5add --- /dev/null +++ b/ui/trading/ts/copy/app.ts @@ -0,0 +1,63 @@ +export const appName = 'Statoblast trading' +export const appHomeLabel = 'Statoblast trading home' +export const brandMark = 'S' +export const primaryNavigationLabel = 'Primary' +export const markets = 'Markets' +export const market = 'Market' +export const liquidity = 'Liquidity' +export const portfolio = 'Portfolio' +export const deploy = 'Deploy' +export const help = 'Help' +export const securityPool = 'Security pool' +export const notFound = 'Not found' +export const pageNotFound = 'Page not found' +export const returnToMarkets = 'Return to markets' +export const backToMarkets = '← Markets' +export const disconnectWallet = 'Disconnect wallet' +export const connectWallet = 'Connect wallet' +export const positionsByPool = 'Positions by SecurityPool' +export const standaloneLiveClient = 'Standalone live client' +export const contractsUnavailable = 'Trading contracts unavailable' +export const checkingContracts = 'Checking deterministic trading contracts…' +export const poolDataUnavailable = 'Pool data unavailable' +export const separateInvalidAccounting = 'Separate INVALID accounting' +export const liquidityDescription = 'LP tokens represent only YES and NO reserves.' +export const removalPreview = 'Removal preview' +export const projectGuide = 'Project guide' +export const marketGuide = 'How the market works' +export const deploymentDescription = 'Deploy and verify the shared deterministic contracts that back the application.' +export const marketGuideSteps = [ + { + number: '01', + title: 'Create a complete set', + description: 'Your ETH is sent to the selected Statoblast security pool, which creates equal amounts of INVALID, YES, and NO at its current exchange rate.', + }, + { + number: '02', + title: 'Trade one direction', + description: 'The opposite share enters the constant-product pair. You receive extra shares of your selected outcome.', + }, + { + number: '03', + title: 'Retain INVALID', + description: 'Matching INVALID stays in your wallet and is required alongside YES and NO to redeem a complete set.', + }, + { + number: '04', + title: 'Exit a covered amount', + description: 'The router buys the missing opposite share, combines a full set, and redeems current collateral value to ETH.', + }, +] as const +export const priceMeaningTitle = 'What the price means' +export const priceMeaningDescription = 'Conditional YES and NO prices sum to 100% because the pair compares only valid outcomes. This does not say INVALID has zero probability; the AMM has no invalidity estimate at all.' +export const remainingSharesTitle = 'Why profit can remain as shares' +export const remainingSharesDescription = + 'An insured ETH exit requires one INVALID for every complete set redeemed. If a profitable position contains more directional shares than matching INVALID, the excess remains transferable but cannot be converted into complete sets without acquiring more INVALID. After resolution, those excess shares redeem collateral only if their outcome won.' + +export function documentTitle(pageTitle: string) { + return `${pageTitle} · ${appName}` +} + +export function disconnectWalletLabel(account: string) { + return `${disconnectWallet} ${account}` +} diff --git a/ui/trading/ts/copy/common.ts b/ui/trading/ts/copy/common.ts new file mode 100644 index 000000000..db7e045c4 --- /dev/null +++ b/ui/trading/ts/copy/common.ts @@ -0,0 +1,5 @@ +export const deploymentVerified = 'Deployment verified' +export const conditionalPricesOnly = 'Conditional prices only' +export const developerDocumentation = 'The complete developer and protocol documentation is included in' +export const developerDocumentationPath = 'solidity/docs/trading/' +export const formatNetworkConditionalPrices = (chainName: string) => `${chainName} · conditional prices only` diff --git a/ui/trading/ts/copy/forkMigration.ts b/ui/trading/ts/copy/forkMigration.ts new file mode 100644 index 000000000..b9c5ffd8e --- /dev/null +++ b/ui/trading/ts/copy/forkMigration.ts @@ -0,0 +1,19 @@ +export const childPoolMissing = 'Child pool missing' +export const childPoolReady = 'Child pool ready' +export const noBranchesSelected = 'No child branches selected.' +export const exactTickPrompt = 'Enter an exact tick' +export const invalidTick = 'Invalid' +export const removeScalarTarget = 'Remove scalar target' +export const addScalarTarget = 'Add scalar target' +export const scalarForkTick = 'Scalar fork tick' +export const deployedScalarChildren = 'Deployed scalar children' +export const scalarForkQuestion = 'Scalar fork question' +export const categoricalForkQuestion = 'Categorical fork question' + +export function tickPosition(tick: bigint, numTicks: bigint) { + return `Tick ${tick.toString()} of ${numTicks.toString()}` +} + +export function selectedTargetCount(count: number) { + return count === 1 ? '1 target selected' : `${count.toString()} targets selected` +} diff --git a/ui/trading/ts/copy/workflows.ts b/ui/trading/ts/copy/workflows.ts new file mode 100644 index 000000000..79de420a8 --- /dev/null +++ b/ui/trading/ts/copy/workflows.ts @@ -0,0 +1,32 @@ +export const tradeSummary = 'Trade summary' +export const youPay = 'You pay' +export const youReceive = 'You receive' +export const youUse = 'You use' +export const invalidReceived = 'INVALID received' +export const invalidRequired = 'INVALID required' +export const tradingFee = 'Trading fee' +export const transaction = 'Transaction' +export const liquidityTransaction = 'Liquidity transaction' +export const approveExactLp = 'Approve exact LP amount' +export const approvingExactLp = 'Approving exact LP amount…' +export const simulateLiquidity = 'Simulate liquidity transaction' +export const simulatingLiquidity = 'Simulating liquidity transaction…' +export const submitLiquidity = 'Submit liquidity transaction' +export const submittingLiquidity = 'Submitting liquidity transaction…' +export const approveSettlement = 'Approve router for complete-set redemption' +export const approveOutcomeTokens = 'Approve router for all outcome tokens' +export const approvingRouter = 'Approving router…' +export const simulateSettlement = 'Simulate authoritative settlement' +export const simulatingSettlement = 'Simulating authoritative settlement…' +export const submitSettlement = 'Submit settlement transaction' +export const submittingSettlement = 'Submitting settlement transaction…' +export const previewTrade = 'Preview trade' +export const submittingTrade = 'Submitting trade…' + +export function migrationSubmission(count: number) { + return `Submit migration to ${count.toString()} child ${count === 1 ? 'branch' : 'branches'}` +} + +export function simulatingTrade(mode: 'entry' | 'exit', side: 'YES' | 'NO') { + return `Simulating ${mode === 'entry' ? `Enter ${side}` : `insured ${side} exit`}…` +} diff --git a/trading/ui/ts/demo/markets.ts b/ui/trading/ts/demo/markets.ts similarity index 95% rename from trading/ui/ts/demo/markets.ts rename to ui/trading/ts/demo/markets.ts index 548f8e11b..75bc56b94 100644 --- a/trading/ui/ts/demo/markets.ts +++ b/ui/trading/ts/demo/markets.ts @@ -37,13 +37,6 @@ export const demoWalletBalances = { lp: 428_570_000_000_000_000_000n, } as const -export const demoWalletAccount = '0x8ba1f109551bD432803012645Ac136ddd64DBA72' as const -export const demoWalletEthAttoEth = 64n * 10n ** 18n - -export function demoWalletRepAttoRep(universeId: bigint) { - return (universeId === 2n ? 1_750n : 12_500n) * 10n ** 18n -} - const pool = `0x${'3a'.repeat(20)}` as const const pair = `0x${'7c'.repeat(20)}` as const const childPool = `0x${'4b'.repeat(20)}` as const diff --git a/trading/ui/ts/features/ForkMigrationTargets.tsx b/ui/trading/ts/features/ForkMigrationTargets.tsx similarity index 52% rename from trading/ui/ts/features/ForkMigrationTargets.tsx rename to ui/trading/ts/features/ForkMigrationTargets.tsx index dff36c194..3a4f121c3 100644 --- a/trading/ui/ts/features/ForkMigrationTargets.tsx +++ b/ui/trading/ts/features/ForkMigrationTargets.tsx @@ -1,8 +1,11 @@ -import { formatScalarOutcomeLabel, getScalarOutcomeIndex, MAX_PRECISE_SCALAR_TICK_COUNT } from '@zoltar/shared/scalarOutcome' +import { formatScalarOutcomeLabel, getScalarOutcomeIndex } from '@zoltar/shared/scalarOutcome' +import { OutcomeSelectionList } from '@zoltar/ui-core-shared/components/OutcomeSelectionList.js' +import { ScalarOutcomePicker } from '@zoltar/ui-core-shared/components/ScalarOutcomePicker.js' import { useMemo, useState } from 'preact/hooks' -import { createScalarForkTarget, type ForkMigrationContext, type ForkTarget } from '../protocol/forks.ts' +import { createScalarForkTarget, type ForkMigrationContext, type ForkTarget } from '../protocol/forks.js' +import * as forkCopy from '../copy/forkMigration.js' -export type { ForkMigrationContext, ForkTarget } from '../protocol/forks.ts' +export type { ForkMigrationContext, ForkTarget } from '../protocol/forks.js' function sameTarget(left: ForkTarget, right: ForkTarget) { return left.outcomeIndex === right.outcomeIndex @@ -13,44 +16,25 @@ function toggleTarget(selectedTargets: readonly ForkTarget[], target: ForkTarget } function TargetStatus({ target }: { target: ForkTarget }) { - return {target.canonicalPool === undefined ? 'Child pool missing' : 'Child pool ready'} + return {target.canonicalPool === undefined ? forkCopy.childPoolMissing : forkCopy.childPoolReady} } function SelectedTargets({ selectedTargets, disabled, onChange }: { selectedTargets: readonly ForkTarget[]; disabled: boolean; onChange(targets: readonly ForkTarget[]): void }) { - if (selectedTargets.length === 0) return

No child branches selected.

return ( -
    - {selectedTargets.map(target => ( -
  • -
    - {target.label} - -
    - -
  • - ))} -
+ ({ key: target.outcomeIndex.toString(), label: target.label, details: , disabled, selected: true, onSelect: () => onChange(toggleTarget(selectedTargets, target)) }))} + /> ) } function CategoricalTargets({ context, selectedTargets, disabled, onChange }: { context: Extract; selectedTargets: readonly ForkTarget[]; disabled: boolean; onChange(targets: readonly ForkTarget[]): void }) { return ( -
- {context.availableTargets.map(target => { - const selected = selectedTargets.some(candidate => sameTarget(candidate, target)) - return ( - - ) - })} -
+ ({ key: target.outcomeIndex.toString(), label: target.label, details: , disabled, selected: selectedTargets.some(candidate => sameTarget(candidate, target)), onSelect: () => onChange(toggleTarget(selectedTargets, target)) }))} + /> ) } @@ -67,45 +51,35 @@ function ScalarTargets({ context, selectedTargets, disabled, onChange }: { conte else if (tick !== undefined) outcomeIndex = getScalarOutcomeIndex(context, tick) const candidate = outcomeIndex === undefined ? undefined : createScalarForkTarget(context, outcomeIndex) const candidateSelected = candidate !== undefined && selectedTargets.some(target => sameTarget(target, candidate)) - let candidateLabel = 'Enter an exact tick' - if (invalid) candidateLabel = 'Invalid' + let candidateLabel = forkCopy.exactTickPrompt + if (invalid) candidateLabel = forkCopy.invalidTick else if (tick !== undefined) candidateLabel = formatScalarOutcomeLabel(context, tick) - const inputType = context.numTicks <= MAX_PRECISE_SCALAR_TICK_COUNT ? 'range' : 'text' + let selectedTickLabel = forkCopy.exactTickPrompt + if (invalid) selectedTickLabel = forkCopy.invalidTick + else if (tick !== undefined) selectedTickLabel = forkCopy.tickPosition(tick, context.numTicks) return (
- - -
- {candidateSelected ? 'Selected branch' : 'Branch to add'} - {candidateLabel} - {tick === undefined || invalid ? null : ( - - Tick {tick.toString()} of {context.numTicks.toString()} - - )} -
- + candidate === undefined || onChange(toggleTarget(selectedTargets, candidate))}> + {candidateSelected ? forkCopy.removeScalarTarget : forkCopy.addScalarTarget} + + } + clampExactTickInput={false} + details={{ answerUnit: context.answerUnit, displayValueMax: context.displayValueMax, displayValueMin: context.displayValueMin, maxValueLabel: formatScalarOutcomeLabel(context, context.numTicks), minValueLabel: formatScalarOutcomeLabel(context, 0n), numTicks: context.numTicks }} + disabled={disabled} + isInvalid={invalid} + label={forkCopy.scalarForkTick} + onInvalidChange={setInvalid} + onSelectedTickChange={setTickInput} + selectedOutcomeLabel={candidateLabel} + selectedTick={tickInput} + selectedTickLabel={selectedTickLabel} + /> {context.availableTargets.length === 0 ? null : (
- Deployed scalar children + {forkCopy.deployedScalarChildren} {context.availableTargets.map(target => ( - )} -
- + ) + } + />
) let discoveryContent @@ -580,18 +600,20 @@ export function LiveTrading({ if (routePool !== undefined) { if (selected !== undefined) return ( - + ) return (
-
-
- - ← Markets - -

Security pool

-
-
+ {appCopy.backToMarkets}} title={appCopy.securityPool} /> {connectionMessage === undefined ? null : (
) } - if (route === 'portfolio') + if (route === 'portfolio') { + const subtitle = portfolioRouteSubtitle(configuration.chainName, getActiveSimulationController() !== undefined) return (
-
-
- Positions by SecurityPool -

Portfolio

-

{configuration.chainName}

-
- {walletConnectRequestNonce === undefined ? ( - - ) : null} -
+ + {account === undefined ? appCopy.connectWallet : shortAddress(account)} + + ) : undefined + } + /> {message === undefined ? null : (
) + } return (
-
-
-

Markets

-

{configuration.chainName} · conditional prices only

-
- {walletConnectRequestNonce === undefined ? ( - - ) : null} -
+ + {account === undefined ? appCopy.connectWallet : shortAddress(account)} + + ) : undefined + } + /> {message === undefined && parsedAmount.error === undefined ? null : (
Security pool
@@ -764,6 +788,7 @@ export function LiveTrading({ createGuardedWalletWrite={createGuardedWalletWrite} retryBalances={retryBalances} onWorkflowLockChange={updateLiquidityWorkflowLock} + services={settlementServices} /> )} {route === 'liquidity' ? ( @@ -785,6 +810,7 @@ export function LiveTrading({ createGuardedWalletWrite={createGuardedWalletWrite} retryBalances={retryBalances} onWorkflowLockChange={updateLiquidityWorkflowLock} + services={liquidityServices} /> ) : null} {route !== 'liquidity' && route !== 'portfolio' && selectedPairInitialized ? ( @@ -825,6 +851,20 @@ export function LiveTrading({ type LiquidityQuote = Awaited> & QuoteContext +type LiveLiquidityServices = Readonly<{ + approveLpRouter: typeof approveLpRouter + publicErrorMessage: typeof publicErrorMessage + simulateLiquidity: typeof simulateLiquidity + submitFreshLiquidity: typeof submitFreshLiquidity +}> + +export const liveLiquidityServices: LiveLiquidityServices = { + approveLpRouter, + publicErrorMessage, + simulateLiquidity, + submitFreshLiquidity, +} + export function liquidityApprovalRequired(balanceState: BalanceState, operation: LiquidityOperation, amount: bigint | undefined, allowance: bigint | undefined) { return balanceState === 'ready' && operation === 'remove' && amount !== undefined && amount > 0n && allowance !== undefined && allowance < amount } @@ -851,6 +891,7 @@ function LiveLiquidityControls({ createGuardedWalletWrite, retryBalances, onWorkflowLockChange, + services = liveLiquidityServices, }: { configuration: DeploymentConfiguration market: LiveMarket @@ -869,6 +910,7 @@ function LiveLiquidityControls({ createGuardedWalletWrite(account: Address, networkFailure: string, accountFailure: string): GuardedWalletWrite retryBalances(): Promise onWorkflowLockChange(locked: boolean): void + services?: LiveLiquidityServices }) { const defaultOperation: LiquidityOperation = market.pair === undefined || market.lpTotalSupply === 0n ? 'initialize' : 'add' const [operation, setOperation] = useState(defaultOperation) @@ -937,7 +979,7 @@ function LiveLiquidityControls({ setState('simulating') setTransactionHash(undefined) setError(undefined) - const simulated = await simulateLiquidity(walletClient, configuration, market, account, operation, parsed, conditionalBps ?? 5_000n, validityMinutes, slippageBps) + const simulated = await services.simulateLiquidity(walletClient, configuration, market, account, operation, parsed, conditionalBps ?? 5_000n, validityMinutes, slippageBps) const nextQuote: LiquidityQuote = { ...simulated, account, configuration, walletClient } if (!simulationRequests.isCurrent(request)) return setQuote(nextQuote) @@ -945,7 +987,7 @@ function LiveLiquidityControls({ } catch (caught) { if (!simulationRequests.isCurrent(request)) return setState('error') - setError(publicErrorMessage(caught, 'Liquidity simulation failed')) + setError(services.publicErrorMessage(caught, 'Liquidity simulation failed')) } } @@ -968,7 +1010,7 @@ function LiveLiquidityControls({ 'Wallet account changed; reconnect before approving', )(async () => { setState('approval') - return await approveLpRouter(walletClient, configuration, market, account, parsed) + return await services.approveLpRouter(walletClient, configuration, market, account, parsed) }) setTransactionHash(broadcastHash) setState('approval-pending') @@ -1047,7 +1089,7 @@ function LiveLiquidityControls({ simulationRequests.invalidate() await executeWithCurrentWalletContext(account, 'Wallet network changed; switch back before submitting', 'Wallet account changed; reconnect and simulate again', async () => undefined) const guardedLiquidityWrite = createGuardedWalletWrite(account, 'Wallet network changed during liquidity revalidation; reconnect and simulate again', 'Wallet account changed during liquidity revalidation; reconnect and simulate again') - broadcastHash = await submitFreshLiquidity( + broadcastHash = await services.submitFreshLiquidity( walletClient, configuration, account, @@ -1261,43 +1303,30 @@ function LiveLiquidityControls({
)} - {transactionHash === undefined ? null : ( -

- Transaction - {transactionHash} -

- )} - {receiptWarning === undefined ? null : ( - - )} - {error === undefined ? null : ( - - )} + {transactionHash === undefined ? null : } + +

- {stateLabel(state, 'Liquidity transaction')} + {stateLabel(state, workflowCopy.liquidityTransaction)}

- {needsLpApproval ? ( - - ) : null} + {needsLpApproval ? : null} {!needsLpApproval && quote === undefined ? ( - + /> ) : null} {!needsLpApproval && quote !== undefined ? ( - + ) : null} ) @@ -1721,7 +1750,7 @@ export function LiveSettlementControls({ 'Wallet account changed; reconnect before approving', )(async () => { setState('approval') - return await approveRouter(walletClient, market, configuration, account) + return await services.approveRouter(walletClient, market, configuration, account) }) setTransactionHash(broadcastHash) setState('approval-pending') @@ -1874,12 +1903,7 @@ export function LiveSettlementControls({ ) })()} - {transactionHash === undefined ? null : ( -

- Transaction - {transactionHash} -

- )} + {transactionHash === undefined ? null : } {operation === 'redeem-complete-set' ? ( ) : null} - {receiptWarning === undefined ? null : ( - - )} + {balanceState === 'error' ? : null} - {state === 'error' && error !== undefined ? ( - - ) : null} + {!(state === 'error' && error !== undefined) && (balanceState !== 'error' || state === 'confirmed' || state === 'approval-confirmed') && receiptWarning === undefined && !suppressRedundantProtectionStatus ? (

{settlementStatus} @@ -1914,20 +1930,32 @@ export function LiveSettlementControls({ {approvalRequired ? ( <>

This ERC-1155 approval covers every token ID in the pool's share token, including other universe branches. Revoke it through a compatible wallet or share-token contract interface when it is no longer needed.

- + void approveCompleteSetRouter()} + /> ) : null} {!approvalRequired && actionableQuote === undefined ? ( - + void simulateCurrent()} + /> ) : null} {!approvalRequired && actionableQuote !== undefined ? ( - + void submitCurrent()} + /> ) : null} ) @@ -2059,40 +2087,24 @@ function LivePositionControls({ {mode === 'exit' && balances?.approved === false ? ( <>

This ERC-1155 approval covers every token ID in the pool’s share token, including other universe branches. Revoke it through a compatible wallet or share-token contract interface when it is no longer needed.

- + ) : null} {!(mode === 'exit' && balances?.approved === false) && quote === undefined ? ( - - ) : null} - {!(mode === 'exit' && balances?.approved === false) && quote !== undefined ? ( - + /> ) : null} + {!(mode === 'exit' && balances?.approved === false) && quote !== undefined ? : null}

{stateLabel(state, mode === 'entry' ? `Enter ${side}` : `Insured ${side} exit`)}

- {transactionHash === undefined ? null : ( -

- Transaction - {transactionHash} -

- )} - {receiptWarning === undefined ? null : ( - - )} + {transactionHash === undefined ? null : } +
Pool and reserve details {mode === 'entry' ? ( diff --git a/trading/ui/ts/features/MarketDetail.tsx b/ui/trading/ts/features/MarketDetail.tsx similarity index 95% rename from trading/ui/ts/features/MarketDetail.tsx rename to ui/trading/ts/features/MarketDetail.tsx index c745fcbaf..884cf30e1 100644 --- a/trading/ui/ts/features/MarketDetail.tsx +++ b/ui/trading/ts/features/MarketDetail.tsx @@ -1,14 +1,17 @@ import { useEffect, useMemo, useRef, useState } from 'preact/hooks' -import { quoteEnterPosition, quoteExitPosition, maximumInsuredExit, type EnterPositionQuote, type ExitPositionQuote } from '../../../ts/sdk/positions.ts' -import { conditionalYesProbability } from '../../../ts/sdk/math.ts' +import { quoteEnterPosition, quoteExitPosition, maximumInsuredExit, type EnterPositionQuote, type ExitPositionQuote } from '@zoltar/shared/trading/positions' +import { conditionalYesProbability } from '@zoltar/shared/trading/math' import { getScalarOutcomeIndex } from '@zoltar/shared/scalarOutcome' -import type { DemoMarket } from '../demo/markets.ts' -import { demoAttoEthToAttoShares, demoAttoSharesToAttoEth, demoWalletBalances, lifecycleLabel, tradingClosedReason } from '../demo/markets.ts' -import { bigintToSafeNumber, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnits, shortAddress } from '../app/format.ts' -import { SecurityPoolAddressLink, Status } from '../components/Status.tsx' -import { forkMigrationBatchBlocker, forkMigrationBatchWarning, insuredExitLimitMessage } from './LiveTrading.tsx' -import { ForkMigrationTargets, type ForkMigrationContext, type ForkTarget } from './ForkMigrationTargets.tsx' -import { createExclusiveWorkflowGuard } from '../app/latestRequest.ts' +import type { DemoMarket } from '../demo/markets.js' +import { demoAttoEthToAttoShares, demoAttoSharesToAttoEth, demoWalletBalances, lifecycleLabel, tradingClosedReason } from '../demo/markets.js' +import { bigintToSafeNumber, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnits, shortAddress } from '../lib/format.js' +import { SecurityPoolAddressLink, Status } from '../components/Status.js' +import { forkMigrationBatchBlocker, forkMigrationBatchWarning, insuredExitLimitMessage } from './LiveTrading.js' +import { ForkMigrationTargets, type ForkMigrationContext, type ForkTarget } from './ForkMigrationTargets.js' +import { createExclusiveWorkflowGuard } from '../lib/latestRequest.js' +import { RouteHeader } from '@zoltar/ui-core-shared/components/RouteHeader.js' +import * as appCopy from '../copy/app.js' +import { getTradingRouteHref } from '../lib/routing.js' type TransactionState = 'idle' | 'preparing' | 'approval' | 'submitting' | 'pending' | 'confirmed' | 'rejected' | 'reverted' @@ -306,7 +309,7 @@ export function MarketDetail({ market, scenario, onWorkflowLockChange = () => un ) if (!initialized && closedReason === undefined) primaryAction = ( - + {market.pair === undefined ? 'Create and initialize this market in Liquidity' : 'Initialize this pair in Liquidity'} ) @@ -319,15 +322,7 @@ export function MarketDetail({ market, scenario, onWorkflowLockChange = () => un return (
-
-
- - ← Markets - -

{market.question}

-
- {lifecycleLabel(market.lifecycle)} -
+ {appCopy.backToMarkets}} title={market.question} badge={{lifecycleLabel(market.lifecycle)}} />
{!initialized ? ( diff --git a/trading/ui/ts/features/Routes.tsx b/ui/trading/ts/features/Routes.tsx similarity index 82% rename from trading/ui/ts/features/Routes.tsx rename to ui/trading/ts/features/Routes.tsx index 55f86ab8c..3367987f5 100644 --- a/trading/ui/ts/features/Routes.tsx +++ b/ui/trading/ts/features/Routes.tsx @@ -1,18 +1,22 @@ import { useState } from 'preact/hooks' -import { quoteAddLiquidity, quoteInitialLiquidity, quoteRemoveLiquidity } from '../../../ts/sdk/math.ts' -import type { DemoMarket } from '../demo/markets.ts' -import { demoAttoEthToAttoShares, demoWalletBalances, lifecycleLabel } from '../demo/markets.ts' -import { bigintToSafeNumber, formatBpsMultiplier, formatCapacityOwnership, formatEthPerShare, formatMintingCapacity, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnitsOrUndefined } from '../app/format.ts' -import { ProbabilityBar } from '../components/ProbabilityBar.tsx' -import { AddressValue, SecurityPoolAddressLink, Status } from '../components/Status.tsx' -import { maximumInsuredExit } from '../../../ts/sdk/positions.ts' -import { shareBalanceScope } from '../protocol/live.ts' +import { quoteAddLiquidity, quoteInitialLiquidity, quoteRemoveLiquidity } from '@zoltar/shared/trading/math' +import type { DemoMarket } from '../demo/markets.js' +import { demoAttoEthToAttoShares, demoWalletBalances, lifecycleLabel } from '../demo/markets.js' +import { bigintToSafeNumber, formatBpsMultiplier, formatCapacityOwnership, formatEthPerShare, formatMintingCapacity, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnitsOrUndefined } from '../lib/format.js' +import { ProbabilityBar } from '../components/ProbabilityBar.js' +import { AddressValue, SecurityPoolAddressLink, Status } from '../components/Status.js' +import { maximumInsuredExit } from '@zoltar/shared/trading/positions' +import { shareBalanceScope } from '../protocol/live.js' +import { RouteHeader } from '@zoltar/ui-core-shared/components/RouteHeader.js' +import * as appCopy from '../copy/app.js' +import { getTradingRouteHref } from '../lib/routing.js' +export { Help } from './Help.js' function MarketListAction({ market }: { market: DemoMarket }) { const initialized = market.pair !== undefined && market.lpTotalSupply > 0n && market.yesReserve > 0n && market.noReserve > 0n if (initialized) return ( - + Open market → ) @@ -23,7 +27,7 @@ function MarketListAction({ market }: { market: DemoMarket }) { ) return ( - + {market.pair === undefined ? 'Create + initialize →' : 'Initialize pair →'} ) @@ -34,11 +38,7 @@ export function MarketList({ market }: { market: DemoMarket }) { const yesPercent = initialized ? bigintToSafeNumber((market.noReserve * 1_000n) / (market.yesReserve + market.noReserve), 'Conditional YES tenths') / 10 : 0 return (
-
-
-

Markets

-
-
+
{initialized ? :

Conditional price available after initialization.

}
@@ -124,14 +124,7 @@ export function Liquidity({ market }: { market: DemoMarket }) { } return (
-
-
- Separate INVALID accounting -

Liquidity

-

LP tokens represent only YES and NO reserves.

-
- {actionAvailability.remove ? 'Removal preview' : liquidityStatus} -
+ {actionAvailability.remove ? appCopy.removalPreview : liquidityStatus}} />
{closedReason !== undefined && !actionAvailability.remove ? (
@@ -337,15 +330,7 @@ export function SecurityPoolDetails({ market }: { market: DemoMarket }) { const scope = shareBalanceScope(market) return (
-
-
- - ← Markets - -

Security pool

-

{market.question}

-
-
+ {appCopy.backToMarkets}} title={appCopy.securityPool} description={market.question} />
@@ -409,61 +394,10 @@ export function SecurityPoolDetails({ market }: { market: DemoMarket }) { export function Portfolio({ market }: { market: DemoMarket }) { return (
-
-
-

Portfolio

-
-
+
) } - -export function Help() { - return ( -
-
-
- Project guide -

How the market works

-
-
-
-
- 01 -

Create a complete set

-

Your ETH is sent to the selected Statoblast security pool, which creates equal amounts of INVALID, YES, and NO at its current exchange rate.

-
-
- 02 -

Trade one direction

-

The opposite share enters the constant-product pair. You receive extra shares of your selected outcome.

-
-
- 03 -

Retain INVALID

-

Matching INVALID stays in your wallet and is required alongside YES and NO to redeem a complete set.

-
-
- 04 -

Exit a covered amount

-

The router buys the missing opposite share, combines a full set, and redeems current collateral value to ETH.

-
-
-
-

What the price means

-

Conditional YES and NO prices sum to 100% because the pair compares only valid outcomes. This does not say INVALID has zero probability; the AMM has no invalidity estimate at all.

-

Why profit can remain as shares

-

- An insured ETH exit requires one INVALID for every complete set redeemed. If a profitable position contains more directional shares than matching INVALID, the excess remains transferable but cannot be converted into complete sets without acquiring more INVALID. After resolution, those excess shares redeem - collateral only if their outcome won. -

-

- The complete developer and protocol documentation is included in trading/docs/. -

-
-
- ) -} diff --git a/trading/ui/ts/features/TradingDeploymentSetup.tsx b/ui/trading/ts/features/TradingDeploymentSetup.tsx similarity index 94% rename from trading/ui/ts/features/TradingDeploymentSetup.tsx rename to ui/trading/ts/features/TradingDeploymentSetup.tsx index 841a89a6f..d0e9dfafd 100644 --- a/trading/ui/ts/features/TradingDeploymentSetup.tsx +++ b/ui/trading/ts/features/TradingDeploymentSetup.tsx @@ -1,13 +1,17 @@ import { createPublicClient, http, type Hash, type PublicClient } from '@zoltar/shared/ethereum' +import { getActiveBackend } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import type { ChainBackend } from '@zoltar/ui-core-shared/lib/chainBackend.js' import { createPortal } from 'preact/compat' import { useEffect, useRef, useState } from 'preact/hooks' -import { shortAddress } from '../app/format.ts' -import { AddressValue, Status } from '../components/Status.tsx' -import { parseDeploymentSetupInput, type DeploymentConfiguration } from '../protocol/config.ts' -import { isKnownDefaultRpcUrl, loadCoreDeployments } from '../protocol/coreDeployments.ts' -import { deployTradingStep, deploymentConfigurationForPlan, getTradingDeploymentPlan, loadTradingDeploymentStatus, nextTradingDeploymentStep, type CoreDeployment, type TradingDeploymentPlan, type TradingDeploymentStep } from '../protocol/deployment.ts' -import { createWalletContextSubscription, getInjectedEthereum, type InjectedEthereum } from '../protocol/injected.ts' -import { connectedWalletAccount, connectWallet, createTradingWalletClient, publicErrorMessage, switchWalletChain, validateRpcChainId, walletChainId } from '../protocol/live.ts' +import { shortAddress } from '../lib/format.js' +import { AddressValue, Status } from '../components/Status.js' +import { parseDeploymentSetupInput, type DeploymentConfiguration } from '../protocol/config.js' +import { isKnownDefaultRpcUrl, loadCoreDeployments } from '../protocol/coreDeployments.js' +import { deployTradingStep, deploymentConfigurationForPlan, getTradingDeploymentPlan, loadTradingDeploymentStatus, nextTradingDeploymentStep, type CoreDeployment, type TradingDeploymentPlan, type TradingDeploymentStep } from '../protocol/deployment.js' +import { createWalletContextSubscription, getInjectedEthereum, type InjectedEthereum } from '../protocol/injected.js' +import { connectedWalletAccount, connectWallet, createTradingWalletClient, publicErrorMessage, switchWalletChain, validateRpcChainId, walletChainId } from '../protocol/live.js' +import { RouteHeader } from '@zoltar/ui-core-shared/components/RouteHeader.js' +import * as appCopy from '../copy/app.js' export type TradingDeploymentSetupServices = Readonly<{ createPublicClient(rpcUrl: string): PublicClient @@ -19,8 +23,12 @@ export type TradingDeploymentSetupServices = Readonly<{ export type DeploymentWalletState = Readonly<{ account: string | undefined; connecting: boolean; ready: boolean }> +export function createDeploymentReadClient(rpcUrl: string, backend: Pick = getActiveBackend()): PublicClient { + return backend.id === 'simulation' ? backend.createReadClient() : createPublicClient({ transport: http(rpcUrl) }) +} + const defaultServices: TradingDeploymentSetupServices = { - createPublicClient: rpcUrl => createPublicClient({ transport: http(rpcUrl) }), + createPublicClient: createDeploymentReadClient, connectWallet: async () => { const provider = getInjectedEthereum() if (provider === undefined) throw new Error('No injected wallet was found') @@ -139,10 +147,17 @@ export function TradingDeploymentSetup({ }) useEffect(() => { if (busy || currentConfiguration === undefined) return + const nextChainId = currentConfiguration.chainId.toString() + const nextRpcUrl = currentConfiguration.rpcUrl + const nextRpcOverride = !isKnownDefaultRpcUrl(nextRpcUrl) + if (chainId === nextChainId && rpcUrl === nextRpcUrl) { + if (rpcOverride !== nextRpcOverride) setRpcOverride(nextRpcOverride) + return + } inputRevision.current += 1 - setChainId(currentConfiguration.chainId.toString()) - setRpcUrl(currentConfiguration.rpcUrl) - setRpcOverride(!isKnownDefaultRpcUrl(currentConfiguration.rpcUrl)) + setChainId(nextChainId) + setRpcUrl(nextRpcUrl) + setRpcOverride(nextRpcOverride) }, [busy, currentConfiguration]) const selectedCore = coreDeployments.find(deployment => deployment.chainId.toString() === chainId) useEffect(() => { @@ -452,14 +467,7 @@ export function TradingDeploymentSetup({ return (
{settingsHost === undefined ? null : createPortal(settingsPanel, settingsHost)} -
-
- Standalone live client -

Deploy

-

Deploy and verify the shared deterministic contracts that back the application.

-
- {standaloneWalletButton} -
+
{settingsHost === undefined ? settingsPanel : null} {registryError === undefined ? null : ( diff --git a/trading/ui/ts/features/liveTradingController.ts b/ui/trading/ts/features/liveTradingController.ts similarity index 89% rename from trading/ui/ts/features/liveTradingController.ts rename to ui/trading/ts/features/liveTradingController.ts index 2a0213a26..4d3985153 100644 --- a/trading/ui/ts/features/liveTradingController.ts +++ b/ui/trading/ts/features/liveTradingController.ts @@ -1,8 +1,8 @@ import type { Address, Hash, WalletClient } from '@zoltar/shared/ethereum' import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks' -import { bigintToSafeNumber, parseUnits, parseUnitsOrUndefined } from '../app/format.ts' -import { createExclusiveWorkflowGuard, createLatestRequestGuard } from '../app/latestRequest.ts' -import { getInjectedEthereum, subscribeToWalletContextChanges, type InjectedEthereum, type WalletContextChangeEvent } from '../protocol/injected.ts' +import { parseUnits, parseUnitsOrUndefined } from '../lib/format.js' +import { createExclusiveWorkflowGuard, createLatestRequestGuard } from '../lib/latestRequest.js' +import { getInjectedEthereum, subscribeToWalletContextChanges, type InjectedEthereum, type WalletContextChangeEvent } from '../protocol/injected.js' import { approveRouter, connectWallet, @@ -27,8 +27,9 @@ import { type LiveBalances, type LiveMarket, type SecurityPoolDeployment, -} from '../protocol/live.ts' -import type { DeploymentConfiguration } from '../protocol/config.ts' +} from '../protocol/live.js' +import type { DeploymentConfiguration } from '../protocol/config.js' +import { getActiveSimulationController } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' type EntryQuote = Awaited> type ExitQuote = Awaited> @@ -51,6 +52,42 @@ export type WalletSummaryState = Readonly<{ export type GuardedWalletWrite = (write: () => Promise) => Promise type WorkflowOwner = 'position' | 'liquidity' +export type LiveTradingControllerServices = Readonly<{ + approveRouter: typeof approveRouter + connectWallet: typeof connectWallet + createTradingPublicClient: typeof createTradingPublicClient + createTradingWalletClient: typeof createTradingWalletClient + discoverAllLiveMarketsInUniverse: typeof discoverAllLiveMarketsInUniverse + discoverLiveUniverseMarketPage: typeof discoverLiveUniverseMarketPage + loadLiveBalances: typeof loadLiveBalances + loadWalletHeaderBalances: typeof loadWalletHeaderBalances + simulateEntry: typeof simulateEntry + simulateExit: typeof simulateExit + submitFreshEntry: typeof submitFreshEntry + submitFreshExit: typeof submitFreshExit + switchWalletChain: typeof switchWalletChain + validateLiveDeployment: typeof validateLiveDeployment + walletChainId: typeof walletChainId +}> + +export const liveTradingControllerServices: LiveTradingControllerServices = { + approveRouter, + connectWallet, + createTradingPublicClient, + createTradingWalletClient, + discoverAllLiveMarketsInUniverse, + discoverLiveUniverseMarketPage, + loadLiveBalances, + loadWalletHeaderBalances, + simulateEntry, + simulateExit, + submitFreshEntry, + submitFreshExit, + switchWalletChain, + validateLiveDeployment, + walletChainId, +} + export function walletSummaryRefreshState(account: Address | undefined, universeId: string | undefined): WalletSummaryState { return { account, ethAttoEth: undefined, repAttoRep: undefined, status: account === undefined ? 'disconnected' : 'loading', error: undefined, errorLabel: undefined, universeId } } @@ -276,28 +313,48 @@ function usePositionWorkflowState(onWorkflowLockChange: (locked: boolean) => voi } } -export function useQuestionClock(endTime: bigint | undefined) { - const [nowSeconds, setNowSeconds] = useState(() => BigInt(Math.floor(Date.now() / 1_000))) +export function initialQuestionClockTimestamp(simulationTimestamp: bigint | undefined, currentWallMilliseconds = Date.now()) { + return simulationTimestamp ?? BigInt(Math.floor(currentWallMilliseconds / 1_000)) +} + +export function questionClockShouldPollAgain(endTime: bigint | undefined, currentTimestamp: bigint) { + return endTime === undefined || currentTimestamp < endTime +} + +function useQuestionClock(endTime: bigint | undefined, configuration: DeploymentConfiguration | undefined, services: LiveTradingControllerServices) { + const [nowSeconds, setNowSeconds] = useState(() => initialQuestionClockTimestamp(getActiveSimulationController()?.currentTimestamp)) useEffect(() => { - let timeout: number | undefined + const simulationController = getActiveSimulationController() + if (simulationController !== undefined) { + const update = () => setNowSeconds(simulationController.currentTimestamp) + update() + return simulationController.subscribe(update) + } + if (configuration === undefined) return + const client = services.createTradingPublicClient(configuration) + let timeout: ReturnType | undefined let active = true - const updateAtBoundary = () => { + const updateFromChain = async () => { if (!active) return - const current = BigInt(Math.floor(Date.now() / 1_000)) - setNowSeconds(current) - if (endTime === undefined || current >= endTime) return - const remainingSeconds = endTime - current - const maximumDelay = 2_147_000_000 - const delay = remainingSeconds > BigInt(Math.floor(maximumDelay / 1_000)) ? maximumDelay : bigintToSafeNumber(remainingSeconds, 'Question-end delay') * 1_000 + 50 - timeout = window.setTimeout(updateAtBoundary, delay) + let pollAgain = true + try { + const block = await client.getBlock() + if (!active) return + setNowSeconds(block.timestamp) + pollAgain = questionClockShouldPollAgain(endTime, block.timestamp) + } catch (error) { + void error + } finally { + if (active && pollAgain) timeout = setTimeout(() => void updateFromChain(), 12_000) + } } - updateAtBoundary() + void updateFromChain() return () => { active = false - if (timeout !== undefined) window.clearTimeout(timeout) + if (timeout !== undefined) clearTimeout(timeout) } - }, [endTime]) + }, [configuration, endTime, services]) return nowSeconds } @@ -313,6 +370,7 @@ export function useLiveTradingController({ walletSummaryRetryNonce, defaultSlippage, defaultValidityMinutes, + services = liveTradingControllerServices, }: { route: string configuration: DeploymentConfiguration | undefined @@ -324,6 +382,7 @@ export function useLiveTradingController({ walletSummaryRetryNonce: number defaultSlippage: string defaultValidityMinutes: string + services?: LiveTradingControllerServices }) { const { markets, setMarkets, selectedPool, setSelectedPool, discoveryState, setDiscoveryState, discoveryError, setDiscoveryError, marketPage, setMarketPage, deploymentIndex } = useDiscoveryState() const { @@ -461,7 +520,7 @@ export function useLiveTradingController({ } let chainId: number try { - chainId = await walletChainId(provider) + chainId = await services.walletChainId(provider) } catch (error) { invalidateWalletIdentity(networkFailure) throw new Error(networkFailure, { cause: error }) @@ -473,7 +532,7 @@ export function useLiveTradingController({ } let connectedAccount: Address try { - connectedAccount = await connectWallet(provider) + connectedAccount = await services.connectWallet(provider) } catch (error) { invalidateWalletIdentity(accountFailure) throw new Error(accountFailure, { cause: error }) @@ -527,7 +586,7 @@ export function useLiveTradingController({ let selectedBalanceState = balanceState if (balanceState !== 'error' && balances !== undefined && selectedBalances === undefined) selectedBalanceState = account === undefined ? 'disconnected' : 'loading' const selectedPairInitialized = selected === undefined ? false : livePairInitialized(selected) - const nowSeconds = useQuestionClock(selected?.endTime) + const nowSeconds = useQuestionClock(selected?.endTime, configuration, services) const parsedAmount = useMemo(() => { try { return { value: parseUnits(amount), error: undefined } @@ -566,7 +625,7 @@ export function useLiveTradingController({ return } setWalletSummaryStatus('loading') - void loadWalletHeaderBalances(createTradingPublicClient(configuration), selected, account).then( + void services.loadWalletHeaderBalances(services.createTradingPublicClient(configuration), selected, account).then( loaded => { if (!walletSummaryRequests.isCurrent(request) || accountRef.current !== account) return setWalletEthAttoEth(loaded.ethAttoEth) @@ -609,11 +668,12 @@ export function useLiveTradingController({ setDiscoveryState('loading') setDiscoveryError(undefined) try { - const client = createTradingPublicClient(nextConfiguration) - await validateLiveDeployment(client, nextConfiguration) + const client = services.createTradingPublicClient(nextConfiguration) + await services.validateLiveDeployment(client, nextConfiguration) if (!discoveryRequests.isCurrent(request)) return const requestedUniverseId = parsedUniverseId(selectedUniverseId) - const discovered = route === 'portfolio' || routePool !== undefined ? await discoverAllLiveMarketsInUniverse(client, nextConfiguration, requestedUniverseId, 25n, deploymentIndex) : await discoverLiveUniverseMarketPage(client, nextConfiguration, requestedUniverseId, requestedStart, 25n, deploymentIndex) + const discovered = + route === 'portfolio' || routePool !== undefined ? await services.discoverAllLiveMarketsInUniverse(client, nextConfiguration, requestedUniverseId, 25n, deploymentIndex) : await services.discoverLiveUniverseMarketPage(client, nextConfiguration, requestedUniverseId, requestedStart, 25n, deploymentIndex) if (!discoveryRequests.isCurrent(request)) return if (!discoveryCommitAllowed(owner, positionWorkflowLockedRef.current, liquidityWorkflowLockedRef.current)) { setDiscoveryState('ready') @@ -727,12 +787,12 @@ export function useLiveTradingController({ } setPortfolioBalanceState('loading') setPortfolioBalanceError(undefined) - const client = createTradingPublicClient(configuration) + const client = services.createTradingPublicClient(configuration) void mapWithConcurrency(visibleMarkets, 6, async (market, index) => { if (market.loadError !== undefined) return { market, balances: undefined, error: market.loadError } let entry: PortfolioBalanceEntry try { - const loaded = await loadLiveBalances(client, market, account, configuration.router) + const loaded = await services.loadLiveBalances(client, market, account, configuration.router) entry = { market, balances: liveBalancesForMarket(loaded, market), error: undefined } } catch (error) { entry = { market, balances: undefined, error: publicErrorMessage(error, 'Balance refresh failed') } @@ -771,7 +831,7 @@ export function useLiveTradingController({ setBalanceState('loading') setBalanceError(undefined) setBalances(undefined) - void loadLiveBalances(createTradingPublicClient(configuration), selected, account, configuration.router).then( + void services.loadLiveBalances(services.createTradingPublicClient(configuration), selected, account, configuration.router).then( loaded => { if (balanceRequests.isCurrent(request)) { setBalances(loaded) @@ -803,7 +863,7 @@ export function useLiveTradingController({ setBalanceError(undefined) setBalances(undefined) try { - const loaded = await loadLiveBalances(createTradingPublicClient(configuration), selected, account, configuration.router) + const loaded = await services.loadLiveBalances(services.createTradingPublicClient(configuration), selected, account, configuration.router) if (!balanceRequests.isCurrent(request)) return setBalances(loaded) setBalanceState('ready') @@ -845,25 +905,25 @@ export function useLiveTradingController({ return true } if (configuration === undefined) throw new Error('Deployment configuration is unavailable') - let chainId = await walletChainId(provider) + let chainId = await services.walletChainId(provider) if (!requireCurrentConnection()) return if (chainId !== configuration.chainId) { - await switchWalletChain(provider, configuration.chainId) + await services.switchWalletChain(provider, configuration.chainId) if (!requireCurrentConnection()) return - chainId = await walletChainId(provider) + chainId = await services.walletChainId(provider) if (!requireCurrentConnection()) return } if (chainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const connected = await connectWallet(provider) + const connected = await services.connectWallet(provider) if (!requireCurrentConnection()) return walletSubscriptionCleanup.current?.() walletSubscriptionCleanup.current = subscribeToWalletContextChanges(provider, (eventName: WalletContextChangeEvent) => { walletContextChangeHandler.current(provider, eventName, false) }) - const confirmedChainId = await walletChainId(provider) + const confirmedChainId = await services.walletChainId(provider) if (!requireCurrentConnection()) return if (confirmedChainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const confirmedAccount = await connectWallet(provider) + const confirmedAccount = await services.connectWallet(provider) if (!requireCurrentConnection()) return if (confirmedAccount !== connected) throw new Error('Wallet account changed while connecting; reconnect to continue') balanceRequests.invalidate() @@ -883,7 +943,7 @@ export function useLiveTradingController({ setWalletContextInvalidated(false) walletContextRevision.current++ setAccount(connected) - setWalletClient(createTradingWalletClient(provider, connected)) + setWalletClient(services.createTradingWalletClient(provider, connected)) setWalletProvider(provider) setWalletConnectionFeedback(undefined) setMessage(undefined) @@ -916,19 +976,19 @@ export function useLiveTradingController({ return true } if (configuration === undefined) throw new Error('Deployment configuration is unavailable') - const chainId = await walletChainId(provider) + const chainId = await services.walletChainId(provider) if (!requireCurrentConnection()) return if (chainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const connected = await connectWallet(provider) + const connected = await services.connectWallet(provider) if (!requireCurrentConnection()) return walletSubscriptionCleanup.current?.() walletSubscriptionCleanup.current = subscribeToWalletContextChanges(provider, changedEventName => { walletContextChangeHandler.current(provider, changedEventName, false) }) - const confirmedChainId = await walletChainId(provider) + const confirmedChainId = await services.walletChainId(provider) if (!requireCurrentConnection()) return if (confirmedChainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const confirmedAccount = await connectWallet(provider) + const confirmedAccount = await services.connectWallet(provider) if (!requireCurrentConnection()) return if (confirmedAccount !== connected) throw new Error('Wallet account changed while refreshing; reconnect to continue') balanceRequests.invalidate() @@ -948,7 +1008,7 @@ export function useLiveTradingController({ setWalletContextInvalidated(false) walletContextRevision.current++ setAccount(connected) - setWalletClient(createTradingWalletClient(provider, connected)) + setWalletClient(services.createTradingWalletClient(provider, connected)) setWalletProvider(provider) setWalletConnectionFeedback(undefined) setMessage(undefined) @@ -968,7 +1028,7 @@ export function useLiveTradingController({ setBalanceState('loading') setBalanceError(undefined) try { - const loaded = await loadLiveBalances(createTradingPublicClient(configuration), expectedMarket, expectedAccount, configuration.router) + const loaded = await services.loadLiveBalances(services.createTradingPublicClient(configuration), expectedMarket, expectedAccount, configuration.router) if (accountRef.current !== expectedAccount || !balanceRequests.isCurrent(request)) return 'context-changed' setBalances(loaded) setBalanceState('ready') @@ -995,8 +1055,8 @@ export function useLiveTradingController({ const context = { account, configuration, walletClient } const nextQuote: Quote = mode === 'entry' - ? { ...context, kind: 'entry', value: await simulateEntry(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } - : { ...context, kind: 'exit', value: await simulateExit(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } + ? { ...context, kind: 'entry', value: await services.simulateEntry(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } + : { ...context, kind: 'exit', value: await services.simulateExit(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } if (!simulationRequests.isCurrent(request)) return setQuote(nextQuote) setState('ready') @@ -1027,7 +1087,7 @@ export function useLiveTradingController({ 'Wallet account changed; reconnect before approving', )(async () => { setState('approval') - return await approveRouter(walletClient, selected, configuration, account) + return await services.approveRouter(walletClient, selected, configuration, account) }) setPositionHash(broadcastHash) setState('approval-pending') @@ -1099,7 +1159,7 @@ export function useLiveTradingController({ setState('submitting') return await write() }) - broadcastHash = quote.kind === 'entry' ? await submitFreshEntry(walletClient, configuration, account, quote.value, guardedWrite) : await submitFreshExit(walletClient, configuration, account, quote.value, guardedWrite) + broadcastHash = quote.kind === 'entry' ? await services.submitFreshEntry(walletClient, configuration, account, quote.value, guardedWrite) : await services.submitFreshExit(walletClient, configuration, account, quote.value, guardedWrite) setPositionHash(broadcastHash) setState('pending') const receipt = await observeKnownReceipt(walletClient.waitForTransactionReceipt({ hash: broadcastHash }), refreshWalletSummaryAfterReceipt) diff --git a/ui/trading/ts/index.dev.ts b/ui/trading/ts/index.dev.ts new file mode 100644 index 000000000..b9a7e28be --- /dev/null +++ b/ui/trading/ts/index.dev.ts @@ -0,0 +1,2 @@ +import './liveReload.js' +import './index.js' diff --git a/trading/ui/ts/index.tsx b/ui/trading/ts/index.ts similarity index 66% rename from trading/ui/ts/index.tsx rename to ui/trading/ts/index.ts index 4d37d1144..22f249a4d 100644 --- a/trading/ui/ts/index.tsx +++ b/ui/trading/ts/index.ts @@ -1,8 +1,12 @@ -import { render } from 'preact' +import { createElement } from 'preact' import { createPublicClient, custom, getAddress } from '@zoltar/shared/ethereum' -import { App } from './app/App.tsx' -import type { TradingDeploymentSetupServices } from './features/TradingDeploymentSetup.tsx' -import { CANONICAL_PROXY_DEPLOYER_RUNTIME_CODE, deploymentConfigurationForPlan, getTradingDeploymentPlan } from './protocol/deployment.ts' +import { mountApp } from '@zoltar/ui-core-shared/app/appRoot.js' +import { App } from './app/App.js' +import { initializeTradingActiveEnvironment } from './app/activeEnvironment.js' +import { installTradingRouting } from './lib/routing.js' +import { registerTradingSimulationScenario, TRADING_SIMULATION_SCENARIO } from './simulation/index.js' +import type { TradingDeploymentSetupServices } from './features/TradingDeploymentSetup.js' +import { CANONICAL_PROXY_DEPLOYER_RUNTIME_CODE, deploymentConfigurationForPlan, getTradingDeploymentPlan } from './protocol/deployment.js' const qaDeploymentPending = ['127.0.0.1', 'localhost'].includes(window.location.hostname) && new URLSearchParams(window.location.search).get('qaDeployment') === 'pending' const qaCore = { @@ -48,6 +52,13 @@ function pendingDeploymentFixture() { } } -const root = document.querySelector('#app') -if (!(root instanceof HTMLElement)) throw new Error('Application root is missing') -render(qaDeploymentPending ? : , root) +document.querySelector('body > main')?.remove() +const root = document.querySelector('#app') ?? document.body +installTradingRouting() +registerTradingSimulationScenario() +if (new URLSearchParams(window.location.search).get('simulate') === '1' && !new URLSearchParams(window.location.search).has('simScenario')) { + const url = new URL(window.location.href) + url.searchParams.set('simScenario', TRADING_SIMULATION_SCENARIO) + window.history.replaceState({}, '', url) +} +void mountApp({ initialize: initializeTradingActiveEnvironment, root: () => createElement(App, qaDeploymentPending ? pendingDeploymentFixture() : {}), target: root }) diff --git a/trading/ui/ts/app/format.ts b/ui/trading/ts/lib/format.ts similarity index 100% rename from trading/ui/ts/app/format.ts rename to ui/trading/ts/lib/format.ts diff --git a/trading/ui/ts/app/latestRequest.ts b/ui/trading/ts/lib/latestRequest.ts similarity index 100% rename from trading/ui/ts/app/latestRequest.ts rename to ui/trading/ts/lib/latestRequest.ts diff --git a/ui/trading/ts/lib/routing.ts b/ui/trading/ts/lib/routing.ts new file mode 100644 index 000000000..15b7a986d --- /dev/null +++ b/ui/trading/ts/lib/routing.ts @@ -0,0 +1,27 @@ +import { buildRouteHref, getRouteHashSearch, installRouting } from '@zoltar/ui-core-shared/lib/routing.js' + +export function getTradingRouteHref(routeHash: string) { + return buildRouteHref(routeHash, getRouteHashSearch()) +} + +export function getTradingRoutePath(hash: string) { + return hash.split('?')[0] ?? '' +} + +export function getTradingEnvironmentLocationKey(location: Pick = window.location) { + return `${location.search}|${getRouteHashSearch(location.hash)}` +} + +export function installTradingRouting() { + installRouting({ + defaultRoute: 'markets', + routes: [ + { hash: '#/deploy', name: 'deploy' }, + { hash: '#/markets', name: 'markets' }, + { hash: '#/market', name: 'market' }, + { hash: '#/liquidity', name: 'liquidity' }, + { hash: '#/portfolio', name: 'portfolio' }, + { hash: '#/help', name: 'help' }, + ], + }) +} diff --git a/ui/trading/ts/liveReload.ts b/ui/trading/ts/liveReload.ts new file mode 100644 index 000000000..40c346f7b --- /dev/null +++ b/ui/trading/ts/liveReload.ts @@ -0,0 +1,5 @@ +const eventSource = new EventSource('/__live-reload') + +eventSource.addEventListener('reload', () => { + window.location.reload() +}) diff --git a/trading/ui/ts/protocol/config.ts b/ui/trading/ts/protocol/config.ts similarity index 100% rename from trading/ui/ts/protocol/config.ts rename to ui/trading/ts/protocol/config.ts diff --git a/trading/ui/ts/protocol/coreDeploymentDefaults.ts b/ui/trading/ts/protocol/coreDeploymentDefaults.ts similarity index 100% rename from trading/ui/ts/protocol/coreDeploymentDefaults.ts rename to ui/trading/ts/protocol/coreDeploymentDefaults.ts diff --git a/trading/ui/ts/protocol/coreDeployments.ts b/ui/trading/ts/protocol/coreDeployments.ts similarity index 60% rename from trading/ui/ts/protocol/coreDeployments.ts rename to ui/trading/ts/protocol/coreDeployments.ts index faa825e59..206555a17 100644 --- a/trading/ui/ts/protocol/coreDeployments.ts +++ b/ui/trading/ts/protocol/coreDeployments.ts @@ -1,6 +1,8 @@ import { getAddress, isAddress } from '@zoltar/shared/ethereum' -import { defaultCoreDeploymentRpcUrls } from './coreDeploymentDefaults.ts' -import type { CoreDeployment } from './deployment.ts' +import { defaultCoreDeploymentRpcUrls } from './coreDeploymentDefaults.js' +import type { CoreDeployment } from './deployment.js' +import { getActiveBackend } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { getInfraContractAddresses, PROXY_DEPLOYER_ADDRESS } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' export function isKnownDefaultRpcUrl(rpcUrl: string) { let normalizedRpcUrl: string @@ -32,17 +34,17 @@ export function parseCoreDeployments(candidate: unknown): readonly CoreDeploymen if (!Array.isArray(candidate) || candidate.length === 0) throw new Error('Core deployment registry must contain at least one network') const deployments = candidate.map((value, index) => { if (!isRecord(value)) throw new Error(`Core deployment ${index.toString()} must be an object`) - if (typeof value.chainId !== 'number' || !Number.isSafeInteger(value.chainId) || value.chainId <= 0) throw new Error(`Core deployment ${index.toString()} chainId must be a positive safe integer`) - const chainId = value.chainId - const rpcUrl = value.rpcUrl === undefined ? defaultCoreDeploymentRpcUrls[chainId] : value.rpcUrl + if (typeof value['chainId'] !== 'number' || !Number.isSafeInteger(value['chainId']) || value['chainId'] <= 0) throw new Error(`Core deployment ${index.toString()} chainId must be a positive safe integer`) + const chainId = value['chainId'] + const rpcUrl = value['rpcUrl'] === undefined ? defaultCoreDeploymentRpcUrls[chainId] : value['rpcUrl'] if (rpcUrl === undefined) throw new Error(`Core deployment ${index.toString()} has no default RPC URL`) return { chainId, - chainName: requiredString(value.chainName, `Core deployment ${index.toString()} chainName`), + chainName: requiredString(value['chainName'], `Core deployment ${index.toString()} chainName`), defaultRpcUrl: requiredString(rpcUrl, `Core deployment ${index.toString()} rpcUrl`), - id: requiredString(value.id, `Core deployment ${index.toString()} id`), - proxyDeployer: requiredAddress(value.proxyDeployer, `Core deployment ${index.toString()} proxyDeployer`), - securityPoolFactory: requiredAddress(value.securityPoolFactory, `Core deployment ${index.toString()} securityPoolFactory`), + id: requiredString(value['id'], `Core deployment ${index.toString()} id`), + proxyDeployer: requiredAddress(value['proxyDeployer'], `Core deployment ${index.toString()} proxyDeployer`), + securityPoolFactory: requiredAddress(value['securityPoolFactory'], `Core deployment ${index.toString()} securityPoolFactory`), } }) const chainIds = new Set() @@ -54,6 +56,19 @@ export function parseCoreDeployments(candidate: unknown): readonly CoreDeploymen } export async function loadCoreDeployments() { + const backend = getActiveBackend() + if (backend.id === 'simulation') { + return [ + { + chainId: backend.profile.chain.id, + chainName: backend.profile.displayName, + defaultRpcUrl: 'http://127.0.0.1/', + id: 'simulation', + proxyDeployer: PROXY_DEPLOYER_ADDRESS, + securityPoolFactory: getInfraContractAddresses(backend.profile).securityPoolFactory, + }, + ] satisfies readonly CoreDeployment[] + } const response = await fetch('./core-deployments.json', { cache: 'no-store' }) if (!response.ok) throw new Error(`Core deployment registry failed with HTTP ${response.status.toString()}`) const candidate: unknown = await response.json() diff --git a/trading/ui/ts/protocol/deployment.ts b/ui/trading/ts/protocol/deployment.ts similarity index 93% rename from trading/ui/ts/protocol/deployment.ts rename to ui/trading/ts/protocol/deployment.ts index 37bd2fdb7..4ea2dedb0 100644 --- a/trading/ui/ts/protocol/deployment.ts +++ b/ui/trading/ts/protocol/deployment.ts @@ -1,6 +1,6 @@ import { encodeDeployData, getAddress, getCreate2Address, toHex, type Address, type Hash, type Hex, type PublicClient } from '@zoltar/shared/ethereum' -import { tradingContracts } from '../generated/contractArtifact.ts' -import type { DeploymentConfiguration } from './config.ts' +import { tradingContracts } from '../generated/contractArtifact.js' +import type { DeploymentConfiguration } from './config.js' export type CoreDeployment = Readonly<{ chainId: number @@ -11,7 +11,7 @@ export type CoreDeployment = Readonly<{ securityPoolFactory: Address }> -export type TradingDeploymentStepId = 'factory' | 'router' +type TradingDeploymentStepId = 'factory' | 'router' export type TradingDeploymentStep = Readonly<{ address: Address @@ -33,8 +33,8 @@ type TradingDeploymentWallet = Readonly<{ waitForTransactionReceipt(parameters: Readonly<{ hash: Hash }>): Promise> }> -const factoryContract = tradingContracts['trading/contracts/TwoWayConstantProductFactory.sol'].TwoWayConstantProductFactory -const routerContract = tradingContracts['trading/contracts/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter +const factoryContract = tradingContracts['contracts/trading/TwoWayConstantProductFactory.sol'].TwoWayConstantProductFactory +const routerContract = tradingContracts['contracts/trading/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter const zeroSalt = toHex(0, { size: 32 }) const rpcStateRetryDelaysMilliseconds = [250, 500, 1_000, 2_000, 4_000] as const export const CANONICAL_PROXY_DEPLOYER_RUNTIME_CODE = '0x60003681823780368234f58015156014578182fd5b80825250506014600cf3' satisfies Hex @@ -83,13 +83,13 @@ async function requireCode(client: Pick, address: Addre if (code === undefined || code === '0x') throw new Error(`${label} has no code at ${address}`) } -export async function validateTradingFactory(client: Pick, plan: TradingDeploymentPlan) { +async function validateTradingFactory(client: Pick, plan: TradingDeploymentPlan) { const [securityPoolFactory, feeBps] = await Promise.all([client.readContract({ abi: factoryContract.abi, address: plan.factory.address, functionName: 'securityPoolFactory' }), client.readContract({ abi: factoryContract.abi, address: plan.factory.address, functionName: 'feeBps' })]) if (getAddress(securityPoolFactory) !== plan.core.securityPoolFactory) throw new Error('Trading factory references a different SecurityPoolFactory') if (feeBps !== BigInt(plan.feeBps)) throw new Error('Trading factory fee does not match the selected fee') } -export async function validateTradingRouter(client: Pick, plan: TradingDeploymentPlan) { +async function validateTradingRouter(client: Pick, plan: TradingDeploymentPlan) { const factory = await client.readContract({ abi: routerContract.abi, address: plan.router.address, functionName: 'factory' }) if (getAddress(factory) !== plan.factory.address) throw new Error('Trading router references a different factory') } diff --git a/trading/ui/ts/protocol/forks.ts b/ui/trading/ts/protocol/forks.ts similarity index 99% rename from trading/ui/ts/protocol/forks.ts rename to ui/trading/ts/protocol/forks.ts index a810c20a9..1134b8e70 100644 --- a/trading/ui/ts/protocol/forks.ts +++ b/ui/trading/ts/protocol/forks.ts @@ -1,6 +1,6 @@ import { encodeAbiParameters, getAddress, keccak256, zeroAddress, type Abi, type Address, type PublicClient } from '@zoltar/shared/ethereum' import { formatScalarOutcomeIndexLabel, type ScalarQuestionDetails } from '@zoltar/shared/scalarOutcome' -import type { LiveMarket } from './live.ts' +import type { LiveMarket } from './live.js' const universeComponents = [ { name: 'forkTime', type: 'uint256' }, diff --git a/trading/ui/ts/protocol/injected.ts b/ui/trading/ts/protocol/injected.ts similarity index 92% rename from trading/ui/ts/protocol/injected.ts rename to ui/trading/ts/protocol/injected.ts index 1817ab243..1d1a9b29f 100644 --- a/trading/ui/ts/protocol/injected.ts +++ b/ui/trading/ts/protocol/injected.ts @@ -1,4 +1,5 @@ import type { EIP1193Provider } from '@zoltar/shared/ethereum' +import { getActiveBackend } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' type EthereumEventHandler = (...args: unknown[]) => void @@ -47,5 +48,5 @@ declare global { } export function getInjectedEthereum() { - return window.ethereum + return getActiveBackend().getProvider() ?? window.ethereum } diff --git a/trading/ui/ts/protocol/live.ts b/ui/trading/ts/protocol/live.ts similarity index 94% rename from trading/ui/ts/protocol/live.ts rename to ui/trading/ts/protocol/live.ts index 74dab4c42..2972036cc 100644 --- a/trading/ui/ts/protocol/live.ts +++ b/ui/trading/ts/protocol/live.ts @@ -1,8 +1,9 @@ import { createPublicClient, createWalletClient, custom, getAddress, http, zeroAddress, type Abi, type Address, type Hash, type PublicClient, type WalletClient } from '@zoltar/shared/ethereum' -import { tradingContracts } from '../generated/contractArtifact.ts' -import type { DeploymentConfiguration } from './config.ts' -import type { InjectedEthereum } from './injected.ts' -import { bigintToSafeNumber } from '../app/format.ts' +import { tradingContracts } from '../generated/contractArtifact.js' +import type { DeploymentConfiguration } from './config.js' +import type { InjectedEthereum } from './injected.js' +import { bigintToSafeNumber } from '../lib/format.js' +import { getActiveBackend } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' const deploymentComponents = [ { name: 'securityPool', type: 'address' }, @@ -130,10 +131,10 @@ const shareTokenAbi = [ }, ] as const satisfies Abi -const tradingFactory = tradingContracts['trading/contracts/TwoWayConstantProductFactory.sol'].TwoWayConstantProductFactory -const pair = tradingContracts['trading/contracts/TwoWayConstantProductPair.sol'].TwoWayConstantProductPair -const router = tradingContracts['trading/contracts/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter -export const UI_SLIPPAGE_BPS = 50n +const tradingFactory = tradingContracts['contracts/trading/TwoWayConstantProductFactory.sol'].TwoWayConstantProductFactory +const pair = tradingContracts['contracts/trading/TwoWayConstantProductPair.sol'].TwoWayConstantProductPair +const router = tradingContracts['contracts/trading/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter +const UI_SLIPPAGE_BPS = 50n export function requireTransactionSlippageBps(slippageBps: bigint) { if (slippageBps < 0n || slippageBps > 500n) throw new Error('Slippage must be between 0% and 5%') @@ -255,7 +256,7 @@ export function validateRpcChainId(rpcChainId: number, deploymentChainId: number if (rpcChainId !== deploymentChainId) throw new Error(`RPC chain ${rpcChainId} does not match deployment chain ${deploymentChainId}`) } -export type ShareBalanceScope = Readonly<{ pool: Address; shareToken: Address; invalidTokenId: bigint; yesTokenId: bigint; noTokenId: bigint }> +type ShareBalanceScope = Readonly<{ pool: Address; shareToken: Address; invalidTokenId: bigint; yesTokenId: bigint; noTokenId: bigint }> export type LiveBalances = Readonly<{ scope: ShareBalanceScope; yes: bigint; no: bigint; invalid: bigint; lp: bigint; approved: boolean; lpAllowance: bigint }> @@ -320,10 +321,14 @@ export function settlementAvailability(market: MarketLifecycle, balances: Pick = { key: string | undefined deployments: Deployment[] @@ -624,7 +634,19 @@ export async function refreshSecurityPoolDeploymentIndex( return snapshot } -type RegistryBlockAnchor = Readonly<{ blockNumber: bigint; blockHash: Hash }> +export type RegistryBlockAnchor = Readonly<{ blockNumber: bigint; blockHash: Hash }> + +export async function registryBlockAnchorIsCanonical(anchor: RegistryBlockAnchor, loadLatest: () => Promise, loadByNumber?: (blockNumber: bigint) => Promise) { + const latest = await loadLatest() + if (latest.blockNumber < anchor.blockNumber) return false + if (latest.blockNumber === anchor.blockNumber) return latest.blockHash === anchor.blockHash + if (loadByNumber === undefined) return true + return (await loadByNumber(anchor.blockNumber)).blockHash === anchor.blockHash +} + +export function registrySnapshotBlockParameters(anchor: RegistryBlockAnchor, simulation: boolean): Readonly<{ blockHash?: Hash }> { + return simulation ? {} : { blockHash: anchor.blockHash } +} async function loadAllSecurityPoolDeployments(client: PublicClient, configuration: DeploymentConfiguration, index: SecurityPoolDeploymentIndex, pageSize = 25n) { return await refreshSecurityPoolDeploymentIndex( @@ -632,25 +654,31 @@ async function loadAllSecurityPoolDeployments(client: PublicClient, configuratio `${configuration.chainId}:${configuration.securityPoolFactory}:${configuration.rpcUrl}`, async () => { const anchor = await latestBlockIdentity(client) - const parameters = { abi: securityPoolFactoryAbi, address: configuration.securityPoolFactory, functionName: 'securityPoolDeploymentCount', blockHash: anchor.blockHash } satisfies { abi: typeof securityPoolFactoryAbi; address: Address; functionName: 'securityPoolDeploymentCount'; blockHash: Hash } + const parameters = { abi: securityPoolFactoryAbi, address: configuration.securityPoolFactory, functionName: 'securityPoolDeploymentCount', ...registrySnapshotBlockParameters(anchor, getActiveBackend().id === 'simulation') } satisfies { + abi: typeof securityPoolFactoryAbi + address: Address + functionName: 'securityPoolDeploymentCount' + blockHash?: Hash + } const total = await client.readContract(parameters) return { anchor, total } }, async anchor => { try { - return (await client.getBlock({ blockNumber: anchor.blockNumber })).hash === anchor.blockHash + const loadByNumber = getActiveBackend().id === 'simulation' ? undefined : async (blockNumber: bigint) => await latestBlockIdentity({ getBlock: async () => await client.getBlock({ blockNumber }) }) + return await registryBlockAnchorIsCanonical(anchor, async () => await latestBlockIdentity(client), loadByNumber) } catch (error) { if (error instanceof Error) return false throw error } }, async (start, count, anchor) => { - const parameters = { abi: securityPoolFactoryAbi, address: configuration.securityPoolFactory, functionName: 'securityPoolDeploymentsRange', args: [start, count], blockHash: anchor.blockHash } satisfies { + const parameters = { abi: securityPoolFactoryAbi, address: configuration.securityPoolFactory, functionName: 'securityPoolDeploymentsRange', args: [start, count], ...registrySnapshotBlockParameters(anchor, getActiveBackend().id === 'simulation') } satisfies { abi: typeof securityPoolFactoryAbi address: Address functionName: 'securityPoolDeploymentsRange' args: readonly [bigint, bigint] - blockHash: Hash + blockHash?: Hash } return await client.readContract(parameters) }, @@ -782,10 +810,6 @@ export async function submitFreshExit(client: WalletClient, configuration: Deplo return await guardedWrite(async () => await client.writeContract({ abi: router.abi, address: configuration.router, functionName: 'exitPosition', account, args: [pairAddress, quote.side === 'YES' ? 1 : 2, quote.completeSets, maximumLongShares, minimumEth, account, quote.deadline] })) } -export async function createPair(client: WalletClient, configuration: DeploymentConfiguration, market: LiveMarket, account: Address) { - return await client.writeContract({ abi: tradingFactory.abi, address: configuration.factory, functionName: 'createPair', account, args: [market.pool] }) -} - export async function approveRouter(client: WalletClient, market: LiveMarket, configuration: DeploymentConfiguration, account: Address) { return await client.writeContract({ abi: shareTokenAbi, address: market.shareToken, functionName: 'setApprovalForAll', account, args: [configuration.router, true] }) } diff --git a/ui/trading/ts/simulation/index.ts b/ui/trading/ts/simulation/index.ts new file mode 100644 index 000000000..b5deefec8 --- /dev/null +++ b/ui/trading/ts/simulation/index.ts @@ -0,0 +1,10 @@ +import { registerSimulationScenario } from '@zoltar/ui-core-shared/simulation/scenarios.js' + +export const TRADING_SIMULATION_SCENARIO = 'trading' + +export function registerTradingSimulationScenario() { + registerSimulationScenario(TRADING_SIMULATION_SCENARIO, { + description: 'A seeded Statoblast security pool with the trading factory and router deployed for walletless trade, liquidity, and settlement testing.', + label: 'Trading market', + }) +} diff --git a/ui/trading/ts/simulation/tevmWorker.ts b/ui/trading/ts/simulation/tevmWorker.ts new file mode 100644 index 000000000..c73945ce3 --- /dev/null +++ b/ui/trading/ts/simulation/tevmWorker.ts @@ -0,0 +1,16 @@ +/// + +import { getDeploymentSteps } from '@zoltar/ui-statoblast/protocol/deployment.js' +import { getZoltarAddress } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' +import type { SimulationEngineDependencies } from '@zoltar/ui-core-shared/simulation/tevmEngine.js' +import { applyTradingScenario } from './tradingScenario.js' + +const dependencies: SimulationEngineDependencies = { + applyScenario: applyTradingScenario, + getDeploymentSteps, + getZoltarAddress, +} + +globalThis.zoltarSimulationEngineDependencies = dependencies + +import '@zoltar/ui-core-shared/simulation/tevmWorker.js' diff --git a/ui/trading/ts/simulation/tradingScenario.ts b/ui/trading/ts/simulation/tradingScenario.ts new file mode 100644 index 000000000..0a4eb3a74 --- /dev/null +++ b/ui/trading/ts/simulation/tradingScenario.ts @@ -0,0 +1,34 @@ +import type { BootstrapScenarioApplyParameters } from '@zoltar/ui-core-shared/simulation/bootstrap.js' +import { reportBootstrapProgress, requireQaAccount } from '@zoltar/ui-core-shared/simulation/bootstrap.js' +import { applyStatoblastScenario } from '@zoltar/ui-statoblast/simulation/statoblastScenarios.js' +import { getInfraContractAddresses, PROXY_DEPLOYER_ADDRESS } from '@zoltar/ui-zoltar/protocol/deploymentHelpers.js' +import { deployTradingStep, getTradingDeploymentPlan } from '../protocol/deployment.js' +import { TRADING_SIMULATION_SCENARIO } from './index.js' + +export async function applyTradingScenario(parameters: BootstrapScenarioApplyParameters): Promise { + if (parameters.scenario !== TRADING_SIMULATION_SCENARIO) return false + const seeded = await applyStatoblastScenario({ ...parameters, scenario: 'security-pool' }) + if (!seeded) throw new Error('Trading simulation could not seed its Statoblast security pool') + + const account = requireQaAccount(parameters.accounts[0], 'Expected a Trading simulation QA account') + const addresses = getInfraContractAddresses(parameters.profile) + const plan = getTradingDeploymentPlan( + { + chainId: parameters.profile.chain.id, + chainName: parameters.profile.displayName, + defaultRpcUrl: 'http://127.0.0.1/', + id: 'simulation', + proxyDeployer: PROXY_DEPLOYER_ADDRESS, + securityPoolFactory: addresses.securityPoolFactory, + }, + 30, + ) + const readClient = parameters.createReadClient() + const writeClient = parameters.createWriteClient(account) + await reportBootstrapProgress(parameters.onProgress, 'Deploying Trading factory', 0.96) + await deployTradingStep(writeClient, readClient, plan, plan.factory) + await reportBootstrapProgress(parameters.onProgress, 'Deploying Trading router', 0.98) + await deployTradingStep(writeClient, readClient, plan, plan.router) + await reportBootstrapProgress(parameters.onProgress, 'Trading simulation is ready', 0.995) + return true +} diff --git a/trading/ui/ts/tests/deployment-setup.test.tsx b/ui/trading/ts/tests/app/deployment-setup.test.tsx similarity index 96% rename from trading/ui/ts/tests/deployment-setup.test.tsx rename to ui/trading/ts/tests/app/deployment-setup.test.tsx index 2c6f1bc1f..1ab2e2e85 100644 --- a/trading/ui/ts/tests/deployment-setup.test.tsx +++ b/ui/trading/ts/tests/app/deployment-setup.test.tsx @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { createPublicClient, custom, encodeAbiParameters, getAddress } from '@zoltar/shared/ethereum' import { act } from 'preact/test-utils' -import { installDomEnvironment } from '../../../../ui/ts/tests/testUtils/domEnvironment.ts' -import { App, resolveCanonicalLiveDeployment } from '../app/App.tsx' -import { TradingDeploymentSetup, type TradingDeploymentSetupServices } from '../features/TradingDeploymentSetup.tsx' -import { CANONICAL_PROXY_DEPLOYER_RUNTIME_CODE, deploymentConfigurationForPlan, getTradingDeploymentPlan } from '../protocol/deployment.ts' -import type { InjectedEthereum } from '../protocol/injected.ts' -import { renderIntoDocument } from './test-support/renderIntoDocument.tsx' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { App, resolveCanonicalLiveDeployment } from '../../app/App.js' +import { createDeploymentReadClient, TradingDeploymentSetup, type TradingDeploymentSetupServices } from '../../features/TradingDeploymentSetup.js' +import { CANONICAL_PROXY_DEPLOYER_RUNTIME_CODE, deploymentConfigurationForPlan, getTradingDeploymentPlan } from '../../protocol/deployment.js' +import type { InjectedEthereum } from '../../protocol/injected.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' const core = { chainId: 11_155_111, @@ -84,6 +84,11 @@ async function waitForConnectedWallet(container: HTMLElement) { } describe('trading deployment setup', () => { + test('uses the active TEVM read client instead of the configured HTTP URL', () => { + const client = deploymentClient() + expect(createDeploymentReadClient('http://127.0.0.1/', { createReadClient: () => client, id: 'simulation' })).toBe(client) + }) + test('derives and verifies the canonical CREATE2 trading deployment without configuration', async () => { const plan = getTradingDeploymentPlan(core, 30) let contractReadCount = 0 @@ -138,7 +143,7 @@ describe('trading deployment setup', () => { const rendered = await renderIntoDocument( undefined} services={services} />) cleanupRendered = rendered.cleanup await waitForText('Ready to deploy') - expect(rendered.container.querySelector('h1')?.textContent).toBe('Deploy') + expect(rendered.container.querySelector('.route-header h2')?.textContent).toBe('Deploy') expect(rendered.container.textContent).toContain('Deploy and verify the shared deterministic contracts that back the application.') expect(rendered.container.textContent).not.toContain('No bundled or wallet-deployed trading configuration was found.') expect(rendered.container.textContent).toContain('Trading contracts') @@ -476,7 +481,7 @@ describe('trading deployment setup', () => { test('hydrates the deploy route from asynchronously resolved configuration', async () => { window.history.replaceState(undefined, '', '/?feeBps=99#/deploy') const plan = getTradingDeploymentPlan(core, 30) - const configuration = deploymentConfigurationForPlan(getTradingDeploymentPlan(core, 47), 'https://rpc.example/') + const configuration = deploymentConfigurationForPlan(plan, core.defaultRpcUrl) let contractReadCount = 0 const client = createPublicClient({ transport: custom({ @@ -515,7 +520,7 @@ describe('trading deployment setup', () => { const select = rendered.container.querySelector('.deployment-settings select') const rpcInput = rendered.container.querySelector('.deployment-settings input[type="url"]') expect(select?.value).toBe(core.chainId.toString()) - expect(rpcInput?.value).toBe(configuration.rpcUrl) + expect(rpcInput?.value).toBe(new URL(configuration.rpcUrl).toString()) expect(rendered.container.textContent).not.toContain('Immutable trading fee') expect(rendered.container.textContent).not.toContain('Core network') expect(rendered.container.textContent).not.toContain('Use default RPC') diff --git a/trading/ui/ts/tests/essential-copy.test.tsx b/ui/trading/ts/tests/app/essential-copy.test.tsx similarity index 85% rename from trading/ui/ts/tests/essential-copy.test.tsx rename to ui/trading/ts/tests/app/essential-copy.test.tsx index 7b7f686c4..118ef499b 100644 --- a/trading/ui/ts/tests/essential-copy.test.tsx +++ b/ui/trading/ts/tests/app/essential-copy.test.tsx @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { act } from 'preact/test-utils' -import { installDomEnvironment } from '../../../../ui/ts/tests/testUtils/domEnvironment.ts' -import { App } from '../app/App.tsx' -import { demoMarket } from '../demo/markets.ts' -import { MarketDetail } from '../features/MarketDetail.tsx' -import { ExecutionProtectionFields, renderLiveTradeSummary } from '../features/LiveTrading.tsx' -import { Help, Liquidity, MarketList, Portfolio, SecurityPoolDetails } from '../features/Routes.tsx' -import { renderIntoDocument } from './test-support/renderIntoDocument.tsx' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { App, currentRoute } from '../../app/App.js' +import { demoMarket } from '../../demo/markets.js' +import { MarketDetail } from '../../features/MarketDetail.js' +import { ExecutionProtectionFields, renderLiveTradeSummary } from '../../features/LiveTrading.js' +import { Help, Liquidity, MarketList, Portfolio, SecurityPoolDetails } from '../../features/Routes.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' const forbiddenCopy = [ 'Binary shares for', @@ -67,17 +67,18 @@ describe('essential trading copy', () => { expect(rendered.container.textContent).not.toContain('Fork continuation') }) - test('keeps live-only deployment out of demo navigation without exposing pool internals outside the security pool view', async () => { + test('does not let the removed demo query hide live deployment or invent pool data', async () => { const market = demoMarket('baseline') const rendered = await renderIntoDocument() cleanupRendered = rendered.cleanup expect(rendered.container.querySelector('nav')?.textContent).not.toContain('Developer') - expect(rendered.container.querySelector('nav')?.textContent).not.toContain('Deploy') + expect(rendered.container.querySelector('nav')?.textContent).toContain('Deploy') + expect(rendered.container.querySelector('.demo-banner')).toBeNull() expect(rendered.container.textContent).not.toContain('Outcome token IDs') expect(rendered.container.textContent).not.toContain('System state') expect(rendered.container.textContent).not.toContain('Security multiplier') const poolLink = rendered.container.querySelector(`a[href="#/security-pool/${market.pool}"]`) - expect(poolLink).not.toBeNull() + expect(poolLink).toBeNull() }) test('shows technical identifiers and resolved outcome only in security pool details', async () => { @@ -131,11 +132,17 @@ describe('essential trading copy', () => { test('shows the deployed fee in live entry and exit summaries', async () => { const market = { feeBps: 125n } const entry = await renderIntoDocument(renderLiveTradeSummary({ kind: 'entry', value: { amount: 10n ** 18n, market, result: { totalLongShares: 2n * 10n ** 18n, invalidInsurance: 3n * 10n ** 17n } } }, 'YES')) - expect(entry.container.textContent).toContain('Trading fee 1.25%') + expect(entry.container.textContent).toContain('Trading fee') + expect(entry.container.textContent).toContain('1.25%') + expect(entry.container.textContent).toContain('INVALID received') + expect(entry.container.textContent).not.toContain('INVALID required') await entry.cleanup() const exit = await renderIntoDocument(renderLiveTradeSummary({ kind: 'exit', value: { market, result: { totalLongShares: 2n * 10n ** 18n, invalidInsurance: 3n * 10n ** 17n, ethOut: 8n * 10n ** 17n } } }, 'YES')) cleanupRendered = exit.cleanup - expect(exit.container.textContent).toContain('Trading fee 1.25%') + expect(exit.container.textContent).toContain('Trading fee') + expect(exit.container.textContent).toContain('1.25%') + expect(exit.container.textContent).toContain('INVALID required') + expect(exit.container.textContent).not.toContain('INVALID received') }) test('shows configurable slippage and transaction-validity controls', async () => { @@ -156,11 +163,8 @@ describe('essential trading copy', () => { expect(invalidInputs[1]?.getAttribute('aria-describedby')).toBe(alerts[1]?.id) }) - test('does not preserve removed developer-route selector behavior', async () => { + test('does not preserve removed developer-route behavior', () => { window.history.replaceState(undefined, '', '/?demo=1#/developer') - const rendered = await renderIntoDocument() - cleanupRendered = rendered.cleanup - expect(rendered.container.querySelector('.universe-selector')).not.toBeNull() - expect(rendered.container.querySelector('main')?.textContent).toContain('Markets') + expect(currentRoute()).toBe('markets') }) }) diff --git a/ui/trading/ts/tests/app/sharedIntegration.test.tsx b/ui/trading/ts/tests/app/sharedIntegration.test.tsx new file mode 100644 index 000000000..cab017c3a --- /dev/null +++ b/ui/trading/ts/tests/app/sharedIntegration.test.tsx @@ -0,0 +1,121 @@ +import { expect, test } from 'bun:test' +import { registerTradingSimulationScenario, TRADING_SIMULATION_SCENARIO } from '../../simulation/index.js' +import { getRegisteredSimulationScenarios } from '@zoltar/ui-core-shared/simulation/scenarios.js' +import { tradingActiveEnvironmentDependencies } from '../../app/activeEnvironment.js' +import { AddressValue, Status } from '../../components/Status.js' +import { ReadOnlyAddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { App, currentRoute, tradingNetworkLabel } from '../../app/App.js' +import type { DeploymentConfiguration } from '../../protocol/config.js' +import { getCurrentRouteHash, getRouteHashSearch, resetRoutingForTesting } from '@zoltar/ui-core-shared/lib/routing.js' +import { getTradingRouteHref, installTradingRouting } from '../../lib/routing.js' + +test('Trading registers its shared TEVM scenario and selects its own worker', () => { + registerTradingSimulationScenario() + expect(tradingActiveEnvironmentDependencies.appId).toBe('trading') + expect(getRegisteredSimulationScenarios()).toContain(TRADING_SIMULATION_SCENARIO) +}) + +test('Trading installs shared routing for simulation scenario navigation', () => { + const dom = installDomEnvironment('http://localhost/#/liquidity?simulate=1&simScenario=trading') + try { + installTradingRouting() + expect(getCurrentRouteHash()).toBe('#/liquidity') + expect(getRouteHashSearch()).toBe('?simulate=1&simScenario=trading') + expect(currentRoute()).toBe('liquidity') + expect(getTradingRouteHref('#/markets')).toBe('#/markets?simulate=1&simScenario=trading') + } finally { + resetRoutingForTesting() + dom.cleanup() + } +}) + +test('Trading production links preserve the active simulation route query', async () => { + const productionSources = ['app/App.tsx', 'components/Status.tsx', 'features/LiveTrading.tsx', 'features/MarketDetail.tsx', 'features/Routes.tsx'] + for (const source of productionSources) { + const contents = await readFile(join(import.meta.dir, '../..', source), 'utf8') + expect(contents).not.toMatch(/href=['"]#\//) + } +}) + +test('Trading refreshes the active environment when history changes the simulation scenario', async () => { + const dom = installDomEnvironment('http://localhost/#/markets?simulate=1&simScenario=trading') + installTradingRouting() + let environmentInitializations = 0 + const configuration: DeploymentConfiguration = { + chainId: 31_337, + chainName: 'Browser Simulation', + factory: `0x${'22'.repeat(20)}`, + feeBps: 30, + router: `0x${'33'.repeat(20)}`, + rpcUrl: 'http://127.0.0.1/', + securityPoolFactory: `0x${'11'.repeat(20)}`, + } + const rendered = await renderIntoDocument( + { + environmentInitializations += 1 + }} + loadLiveDeployment={async () => configuration} + />, + ) + try { + window.history.pushState({}, '', '#/markets?simulate=1&simScenario=baseline') + window.dispatchEvent(new Event('popstate')) + await new Promise(resolve => setTimeout(resolve, 10)) + expect(environmentInitializations).toBe(1) + window.dispatchEvent(new Event('popstate')) + await new Promise(resolve => setTimeout(resolve, 10)) + expect(environmentInitializations).toBe(1) + } finally { + await rendered.cleanup() + resetRoutingForTesting() + dom.cleanup() + } +}) + +test('Trading status and address presentation use coreShared primitives', async () => { + const dom = installDomEnvironment() + const address = '0x00000000000000000000000000000000000000a1' + const rendered = await renderIntoDocument(Trading open) + expect(rendered.container.querySelector('.badge.status')?.textContent).toContain('Trading open') + expect(AddressValue({ value: address }).type).toBe(ReadOnlyAddressValue) + await rendered.cleanup() + dom.cleanup() +}) + +test('Trading preserves its route headers and does not restyle shared disclosures', async () => { + const css = await readFile(join(import.meta.dir, '..', '..', '..', 'css', 'app.css'), 'utf8') + expect(css).toContain('.route-header {') + expect(css).toContain('padding: 0;') + expect(css).toContain('background: transparent;') + expect(css).toContain('details:not(.wallet-summary):not(.simulation-banner-details) > summary::after') +}) + +test('verified deployment status stays accurate in live and simulated environments', () => { + expect(tradingNetworkLabel('verified')).toBe('Deployment verified') +}) + +test('the removed demo query cannot select a parallel simulated-data application', async () => { + const dom = installDomEnvironment('http://localhost/?demo=1&scenario=baseline#/markets') + const configuration: DeploymentConfiguration = { + chainId: 31_337, + chainName: 'Browser Simulation', + factory: `0x${'22'.repeat(20)}`, + feeBps: 30, + router: `0x${'33'.repeat(20)}`, + rpcUrl: 'http://127.0.0.1/', + securityPoolFactory: `0x${'11'.repeat(20)}`, + } + const rendered = await renderIntoDocument( configuration} />) + await new Promise(resolve => setTimeout(resolve, 10)) + expect(rendered.container.querySelector('.demo-banner')).toBeNull() + expect(rendered.container.textContent).not.toContain('SIMULATED DATA') + expect(rendered.container.textContent).not.toContain('Demo mode') + expect(rendered.container.textContent).toContain('SecurityPools') + await rendered.cleanup() + dom.cleanup() +}) diff --git a/trading/ui/ts/tests/universe-selector.test.tsx b/ui/trading/ts/tests/app/universe-selector.test.tsx similarity index 70% rename from trading/ui/ts/tests/universe-selector.test.tsx rename to ui/trading/ts/tests/app/universe-selector.test.tsx index cff4568d9..e92b59017 100644 --- a/trading/ui/ts/tests/universe-selector.test.tsx +++ b/ui/trading/ts/tests/app/universe-selector.test.tsx @@ -1,19 +1,18 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { act } from 'preact/test-utils' -import { installDomEnvironment } from '../../../../ui/ts/tests/testUtils/domEnvironment.ts' -import { App, buildLiveUniverseOptions, compactUniqueUniverseIds, compactUniverseId, currentRoute, routeOwnsLiveWallet, tradingDocumentTitle, UniverseSelector, WalletSummary, walletSummaryAfterRouteChange, walletSummaryForUniverse } from '../app/App.tsx' -import { demoMarket } from '../demo/markets.ts' -import { filterMarketsByUniverse, observeKnownReceipt, walletSummaryAvailability, walletSummaryDiscoveryRetryStart, walletSummaryRefreshState } from '../features/LiveTrading.tsx' -import type { DeploymentConfiguration } from '../protocol/config.ts' -import type { LiveMarket } from '../protocol/live.ts' -import { renderIntoDocument } from './test-support/renderIntoDocument.tsx' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { App, buildLiveUniverseOptions, compactUniqueUniverseIds, currentRoute, routeOwnsLiveWallet, tradingDocumentTitle, UniverseSelector, WalletSummary, walletSummaryAfterRouteChange, walletSummaryForUniverse } from '../../app/App.js' +import { filterMarketsByUniverse, observeKnownReceipt, walletSummaryAvailability, walletSummaryDiscoveryRetryStart, walletSummaryRefreshState } from '../../features/LiveTrading.js' +import type { DeploymentConfiguration } from '../../protocol/config.js' +import type { LiveMarket } from '../../protocol/live.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' describe('universe selector', () => { let cleanupDom: (() => void) | undefined let cleanupRendered: (() => Promise) | undefined beforeEach(() => { - cleanupDom = installDomEnvironment('http://localhost/?demo=1#/markets').cleanup + cleanupDom = installDomEnvironment('http://localhost/#/markets').cleanup }) afterEach(async () => { @@ -54,11 +53,16 @@ describe('universe selector', () => { }) test('renders an explicit not-found route and updates the document title', async () => { - window.history.replaceState(undefined, '', '/?demo=1&scenario=loading#/missing') + window.history.replaceState(undefined, '', '/#/missing') expect(currentRoute()).toBe('not-found') const rendered = await renderIntoDocument() cleanupRendered = rendered.cleanup expect(rendered.container.querySelector('main')?.textContent).toContain('Page not found') + expect(rendered.container.querySelector('h1.visually-hidden')?.textContent).toBe('Not found') + const skipButton = Array.from(rendered.container.querySelectorAll('button')).find(button => button.textContent === 'Skip to main content') + if (skipButton === undefined) throw new Error('Shared application skip control is unavailable') + await act(() => skipButton.click()) + expect(document.activeElement).toBe(rendered.container.querySelector('main')) expect(document.title).toBe(tradingDocumentTitle('not-found')) expect(document.title).toBe('Not found · Statoblast trading') }) @@ -151,78 +155,6 @@ describe('universe selector', () => { expect(() => compactUniqueUniverseIds(['7', '7'])).toThrow('Universe IDs must be unique') }) - test('uses the header selection as the demo portfolio universe context', async () => { - window.history.replaceState(undefined, '', '/?demo=1#/portfolio') - const rendered = await renderIntoDocument() - cleanupRendered = rendered.cleanup - const select = rendered.container.querySelector('.universe-selector select') - const main = rendered.container.querySelector('main') - const walletAddress = rendered.container.querySelector('.wallet-summary__address') - const ethDisplay = rendered.container.querySelector('[data-wallet-asset="ETH"]') - const repBalance = rendered.container.querySelector('[data-wallet-asset="REP"]') - expect(walletAddress?.textContent).toBe('0x8ba1f109551bD432803012645Ac136ddd64DBA72') - expect(walletAddress?.textContent).not.toContain('…') - expect(rendered.container.querySelector('.wallet-summary__address--compact')?.textContent).toBe('0x8ba1…BA72') - expect(rendered.container.querySelector('.wallet-summary__details')?.textContent).toContain('Connected account0x8ba1f109551bD432803012645Ac136ddd64DBA72') - expect(ethDisplay?.textContent).toBe('ETH64') - expect(repBalance?.textContent).toBe('REP12,500') - expect(main?.textContent).toContain(demoMarket('baseline').pool) - expect(main?.textContent).not.toContain('Genesis universe') - expect(rendered.container.querySelectorAll('[data-portfolio-pool]')).toHaveLength(1) - await act(() => { - if (select === null) throw new Error('Universe selector is unavailable') - select.value = demoMarket('truth-auction').universeId.toString() - select.dispatchEvent(new Event('change', { bubbles: true })) - }) - expect(main?.textContent).toContain(demoMarket('truth-auction').pool) - expect(main?.textContent).not.toContain(demoMarket('baseline').pool) - expect(repBalance?.textContent).toBe('REP1,750') - }) - - test('keeps an unknown demo pool deep link scoped to its unavailable state', async () => { - const unknownPool = `0x${'99'.repeat(20)}` - window.history.replaceState(undefined, '', `/?demo=1#/security-pool/${unknownPool}`) - const rendered = await renderIntoDocument() - cleanupRendered = rendered.cleanup - expect(rendered.container.querySelector('main')?.textContent).toContain('This security pool is not available in the selected universe.') - expect(rendered.container.querySelector('main')?.textContent).not.toContain('MarketsTrading open') - expect(window.location.hash).toBe(`#/security-pool/${unknownPool}`) - }) - - test('shows scoped unavailability when the selected universe does not contain the routed demo pool', async () => { - const routedPool = demoMarket('baseline').pool - window.history.replaceState(undefined, '', `/?demo=1#/security-pool/${routedPool}`) - const rendered = await renderIntoDocument() - cleanupRendered = rendered.cleanup - expect(rendered.container.textContent).toContain('Security multiplier2×') - const select = rendered.container.querySelector('.universe-selector select') - if (select === null) throw new Error('Universe selector is unavailable') - await act(() => { - select.value = select.options[1]?.value ?? '' - select.dispatchEvent(new Event('change', { bubbles: true })) - }) - expect(rendered.container.querySelector('main')?.textContent).toContain('This security pool is not available in the selected universe.') - expect(rendered.container.querySelector('main')?.textContent).not.toContain('Security multiplier') - expect(window.location.hash).toBe(`#/security-pool/${routedPool}`) - }) - - test('initializes the selector from the scenario universe', async () => { - window.history.replaceState(undefined, '', '/?demo=1&scenario=max-token-ids#/portfolio') - const rendered = await renderIntoDocument() - cleanupRendered = rendered.cleanup - const selected = demoMarket('max-token-ids').universeId.toString() - const selector = rendered.container.querySelector('.universe-selector select') - expect(selector?.value).toBe(selected) - expect(selector?.selectedOptions[0]?.textContent).toBe(`Universe ${compactUniverseId(selected)}`) - expect(selector?.selectedOptions[0]?.getAttribute('aria-label')).toBe(`Universe ${selected}`) - const longOptions = Array.from(selector?.options ?? []).filter(option => option.value.length > 18) - expect(longOptions).toHaveLength(2) - expect(longOptions[0]?.textContent).not.toBe(longOptions[1]?.textContent) - expect(longOptions.every(option => option.textContent?.includes('…') === true)).toBeTrue() - expect(rendered.container.querySelector('main')?.textContent).not.toContain(((1n << 256n) - 256n).toString()) - expect(rendered.container.querySelector(`a[href="#/security-pool/${demoMarket('max-token-ids').pool}"]`)).not.toBeNull() - }) - test('keeps wallet balance failures visible without abbreviating the account', async () => { const account = '0x8ba1f109551bD432803012645Ac136ddd64DBA72' let retries = 0 diff --git a/ui/trading/ts/tests/build/dockerCompose.test.ts b/ui/trading/ts/tests/build/dockerCompose.test.ts new file mode 100644 index 000000000..c6cefe41c --- /dev/null +++ b/ui/trading/ts/tests/build/dockerCompose.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from 'bun:test' +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' + +const composeFile = join(import.meta.dir, '..', '..', '..', 'compose.yaml') +const dockerfile = join(import.meta.dir, '..', '..', '..', '..', 'Dockerfile') +const packageFile = join(import.meta.dir, '..', '..', '..', 'package.json') +const windowsLauncher = join(import.meta.dir, '..', '..', '..', 'start.bat') + +describe('standalone Docker Compose packaging', () => { + test('builds the trading image from the repository root and publishes the UI', async () => { + const source = await readFile(composeFile, 'utf8') + expect(source).toContain('context: ../..') + expect(source).toContain('dockerfile: ui/Dockerfile') + expect(source).toContain('target: local-runtime') + expect(source).not.toContain('TRADING_UI_DEPLOYMENT') + expect(source).toContain('127.0.0.1:4163:8080') + expect(source).toContain('UI_APP: trading') + }) + + test('includes canonical core deployments for browser wallet setup', async () => { + const source = await readFile(dockerfile, 'utf8') + expect(source).toContain('COPY ./docs/mainnet-deployment-addresses.json /source/docs/mainnet-deployment-addresses.json') + expect(source).toContain('COPY ./docs/sepolia-deployment-addresses.json /source/docs/sepolia-deployment-addresses.json') + expect(source).toContain('bun ../coreShared/build/production.mts trading') + expect(source).toContain('/source/ui/trading/dist/ /app/ui/trading/') + }) + + test('builds the standalone image from the repository-root Dockerfile', async () => { + const packageJson = await Bun.file(packageFile).json() + expect(packageJson.scripts['docker:build']).toBe('docker build --file ../Dockerfile --target local-runtime --tag zoltar-trading ../..') + }) + + test('provides a location-independent Windows launcher', async () => { + const source = (await readFile(windowsLauncher, 'utf8')).replaceAll('\r\n', '\n') + expect(source).toContain('pushd "%~dp0"') + expect(source).toContain('docker compose up --build --force-recreate\nset "exit_code=%errorlevel%"\npopd\npause\nexit /b %exit_code%') + }) +}) diff --git a/trading/ui/ts/tests/fork-settlement-context.test.tsx b/ui/trading/ts/tests/features/fork-settlement-context.test.tsx similarity index 89% rename from trading/ui/ts/tests/fork-settlement-context.test.tsx rename to ui/trading/ts/tests/features/fork-settlement-context.test.tsx index 581425816..ecc6a842e 100644 --- a/trading/ui/ts/tests/fork-settlement-context.test.tsx +++ b/ui/trading/ts/tests/features/fork-settlement-context.test.tsx @@ -2,12 +2,12 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { render } from 'preact' import { act } from 'preact/test-utils' import { createPublicClient, createWalletClient, custom, getAddress, type Hash } from '@zoltar/shared/ethereum' -import { installDomEnvironment } from '../../../../ui/ts/tests/testUtils/domEnvironment.ts' -import { LiveSettlementControls } from '../features/LiveTrading.tsx' -import type { DeploymentConfiguration } from '../protocol/config.ts' -import type { ForkMigrationContext } from '../protocol/forks.ts' -import type { LiveMarket } from '../protocol/live.ts' -import { renderIntoDocument } from './test-support/renderIntoDocument.tsx' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { LiveSettlementControls } from '../../features/LiveTrading.js' +import type { DeploymentConfiguration } from '../../protocol/config.js' +import type { ForkMigrationContext } from '../../protocol/forks.js' +import type { LiveMarket } from '../../protocol/live.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' const account = getAddress(`0x${'11'.repeat(20)}`) const pool = getAddress(`0x${'22'.repeat(20)}`) @@ -95,7 +95,7 @@ describe('live fork settlement context', () => { test('retries failed fork metadata and refreshes branches after confirmed migration', async () => { let contextLoads = 0 let refreshes = 0 - const actualLive = await import('../protocol/live.ts') + const actualLive = await import('../../protocol/live.ts') const publicClient = createPublicClient({ transport: custom({ request: async () => undefined }) }) const services = { createPublicClient: () => publicClient, @@ -170,8 +170,8 @@ describe('live fork settlement context', () => { await settleEffects() expect(contextLoads).toBe(2) expect(document.body.textContent).toContain('Which branch wins?') - const target = document.querySelector('input[aria-label="Red fork branch"]') - if (!(target instanceof HTMLInputElement)) throw new Error('Missing categorical fork target') + const target = Array.from(document.querySelectorAll('button')).find(candidate => candidate.textContent?.includes('Red') === true) + if (!(target instanceof HTMLButtonElement)) throw new Error('Missing categorical fork target') await act(() => target.click()) await act(() => button('Simulate authoritative settlement').click()) await settleEffects() diff --git a/trading/ui/ts/tests/forkMigrationTargets.test.tsx b/ui/trading/ts/tests/features/forkMigrationTargets.test.tsx similarity index 69% rename from trading/ui/ts/tests/forkMigrationTargets.test.tsx rename to ui/trading/ts/tests/features/forkMigrationTargets.test.tsx index 97da008ad..e708d864a 100644 --- a/trading/ui/ts/tests/forkMigrationTargets.test.tsx +++ b/ui/trading/ts/tests/features/forkMigrationTargets.test.tsx @@ -2,10 +2,10 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { useState } from 'preact/hooks' import { act } from 'preact/test-utils' import { getScalarOutcomeIndex } from '@zoltar/shared/scalarOutcome' -import { installDomEnvironment } from '../../../../ui/ts/tests/testUtils/domEnvironment.ts' -import { ForkMigrationTargets, type ForkMigrationContext, type ForkTarget } from '../features/ForkMigrationTargets.tsx' -import { DemoScalarForkMigration } from '../features/MarketDetail.tsx' -import { renderIntoDocument } from './test-support/renderIntoDocument.tsx' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { ForkMigrationTargets, type ForkMigrationContext, type ForkTarget } from '../../features/ForkMigrationTargets.js' +import { DemoScalarForkMigration } from '../../features/MarketDetail.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' let cleanup: (() => Promise) | undefined let cleanupDom: (() => void) | undefined @@ -43,6 +43,14 @@ function Harness({ context }: { context: ForkMigrationContext }) { function inputByLabel(container: HTMLElement, labelText: string) { const directlyLabelled = Array.from(container.querySelectorAll('input')).find(candidate => candidate.getAttribute('aria-label') === labelText) if (directlyLabelled instanceof HTMLInputElement) return directlyLabelled + const indirectlyLabelled = Array.from(container.querySelectorAll('input')).find( + candidate => + candidate + .getAttribute('aria-labelledby') + ?.split(' ') + .some(id => container.querySelector(`#${id}`)?.textContent === labelText) === true, + ) + if (indirectlyLabelled instanceof HTMLInputElement) return indirectlyLabelled const label = Array.from(container.querySelectorAll('label')).find(candidate => candidate.textContent?.includes(labelText) === true) const input = label?.querySelector('input') if (!(input instanceof HTMLInputElement)) throw new Error(`Missing input labeled ${labelText}`) @@ -76,7 +84,7 @@ describe('fork migration target selection', () => { const submit = buttonByText(rendered.container, 'Submit migration to 2 child branches') expect(submit.disabled).toBeFalse() expect(rendered.container.textContent).toContain('Authoritative migration simulation ready') - expect(Array.from(rendered.container.querySelectorAll('.fork-target-selection strong')).map(target => target.textContent)).toEqual(['-2.5 °C', '0 °C']) + expect(Array.from(rendered.container.querySelectorAll('.fork-target-selection .migration-outcome-label')).map(target => target.textContent)).toEqual(['-2.5 °C', '0 °C']) }) test('adds many arbitrary scalar outcomes and the invalid branch without raw packed input', async () => { @@ -89,33 +97,43 @@ describe('fork migration target selection', () => { await input(tickInput, tick) await click(buttonByText(rendered.container, 'Add scalar target')) } - await click(inputByLabel(rendered.container, 'Invalid fork outcome')) + await click(inputByLabel(rendered.container, 'Invalid')) await click(buttonByText(rendered.container, 'Add scalar target')) - expect(Array.from(rendered.container.querySelectorAll('button')).filter(button => button.textContent?.includes('Remove target') === true)).toHaveLength(6) + expect(rendered.container.querySelectorAll('.fork-target-selection button')).toHaveLength(6) expect(rendered.container.textContent).toContain('Invalid') expect(rendered.container.textContent).toContain('-50 °C') expect(rendered.container.textContent).toContain('50 °C') expect(rendered.container.textContent).not.toContain(getScalarOutcomeIndex(context, 50n).toString()) - expect(Array.from(rendered.container.querySelectorAll('.fork-target-selection strong')).map(target => target.textContent)).toEqual(['-50 °C', '-25 °C', '0 °C', '25 °C', '50 °C', 'Invalid']) + expect(Array.from(rendered.container.querySelectorAll('.fork-target-selection .migration-outcome-label')).map(target => target.textContent)).toEqual(['-50 °C', '-25 °C', '0 °C', '25 °C', '50 °C', 'Invalid']) }) test('rejects a scalar tick beyond the fork question range instead of silently changing it', async () => { const rendered = await renderIntoDocument() cleanup = rendered.cleanup - await input(inputByLabel(rendered.container, 'Scalar fork tick'), (BigInt(Number.MAX_SAFE_INTEGER) + 2n).toString()) + const tickInput = inputByLabel(rendered.container, 'Scalar fork tick') + const outOfRangeTick = (BigInt(Number.MAX_SAFE_INTEGER) + 2n).toString() + await input(tickInput, outOfRangeTick) + const selectedOutcomeLabel = Array.from(rendered.container.querySelectorAll('.metric-label')).find(label => label.textContent === 'Selected Outcome') + const selectedOutcomeValue = selectedOutcomeLabel?.parentElement?.querySelector('.metric-field-value') + expect(tickInput.value).toBe(outOfRangeTick) expect(buttonByText(rendered.container, 'Add scalar target').disabled).toBeTrue() expect(rendered.container.textContent).toContain('Enter an exact tick') + expect(selectedOutcomeValue?.textContent).toBe('Enter an exact tick') + await act(() => tickInput.dispatchEvent(new Event('blur', { bubbles: true }))) + expect(tickInput.value).toBe(outOfRangeTick) + expect(selectedOutcomeValue?.textContent).toBe('Enter an exact tick') }) - test('distinguishes an uncommitted candidate and exposes selected child shortcuts as pressed', async () => { + test('exposes selected child shortcuts without redundant candidate status copy', async () => { const context = scalarContext() const readyTarget: ForkTarget = { outcomeIndex: getScalarOutcomeIndex(context, 25n), universeId: 11n, label: '-25 °C', canonicalPool: `0x${'11'.repeat(20)}` } const rendered = await renderIntoDocument( undefined} />) cleanup = rendered.cleanup - expect(rendered.container.textContent).toContain('Branch to add') + expect(rendered.container.textContent).not.toContain('Branch to add') + expect(rendered.container.textContent).not.toContain('Selected branch') const shortcut = buttonByText(rendered.container, '-25 °C') expect(shortcut.getAttribute('aria-pressed')).toBe('true') }) @@ -140,11 +158,21 @@ describe('fork migration target selection', () => { const rendered = await renderIntoDocument() cleanup = rendered.cleanup - await click(inputByLabel(rendered.container, 'Red fork branch')) - await click(inputByLabel(rendered.container, 'Blue fork branch')) + expect(rendered.container.querySelector('.migration-outcome-list')).not.toBeNull() + + await click(buttonByText(rendered.container, 'Red')) + await click(buttonByText(rendered.container, 'Blue')) - expect(inputByLabel(rendered.container, 'Red fork branch').checked).toBeTrue() - expect(inputByLabel(rendered.container, 'Blue fork branch').checked).toBeTrue() + expect(buttonByText(rendered.container, 'Red').getAttribute('aria-pressed')).toBe('true') + expect(buttonByText(rendered.container, 'Blue').getAttribute('aria-pressed')).toBe('true') expect(rendered.container.textContent).toContain('2 targets selected') }) + + test('uses the shared scalar outcome picker while keeping fork target conversion local', async () => { + const rendered = await renderIntoDocument() + cleanup = rendered.cleanup + + expect(rendered.container.querySelector('.market-scalar-deploy')).not.toBeNull() + expect(inputByLabel(rendered.container, 'Scalar fork tick').getAttribute('type')).toBe('range') + }) }) diff --git a/trading/ui/ts/tests/live-settlement.test.ts b/ui/trading/ts/tests/features/live-settlement.test.ts similarity index 97% rename from trading/ui/ts/tests/live-settlement.test.ts rename to ui/trading/ts/tests/features/live-settlement.test.ts index b9f342e49..786a172f7 100644 --- a/trading/ui/ts/tests/live-settlement.test.ts +++ b/ui/trading/ts/tests/features/live-settlement.test.ts @@ -1,9 +1,9 @@ import { describe, expect, test } from 'bun:test' import { createWalletClient, custom, decodeFunctionData, encodeAbiParameters, type Address, type Hex } from '@zoltar/shared/ethereum' -import { tradingContracts } from '../generated/contractArtifact.ts' -import { settlementQuoteCanSubmit, settlementQuoteMatchesInputs } from '../features/LiveTrading.tsx' -import { normalizeForkOutcomeIndexes, simulateSettlement, submitFreshSettlement, type LiveMarket } from '../protocol/live.ts' -import type { DeploymentConfiguration } from '../protocol/config.ts' +import { tradingContracts } from '../../generated/contractArtifact.js' +import { settlementQuoteCanSubmit, settlementQuoteMatchesInputs } from '../../features/LiveTrading.js' +import { normalizeForkOutcomeIndexes, simulateSettlement, submitFreshSettlement, type LiveMarket } from '../../protocol/live.js' +import type { DeploymentConfiguration } from '../../protocol/config.js' const account = `0x${'11'.repeat(20)}` as Address const shareToken = `0x${'22'.repeat(20)}` as Address @@ -11,7 +11,7 @@ const pool = `0x${'33'.repeat(20)}` as Address const transactionHash = `0x${'44'.repeat(32)}` as Hex const blockHash = `0x${'55'.repeat(32)}` as Hex const configuration: DeploymentConfiguration = { chainId: 1, chainName: 'Test', rpcUrl: 'http://localhost', securityPoolFactory: `0x${'77'.repeat(20)}`, factory: `0x${'88'.repeat(20)}`, router: `0x${'99'.repeat(20)}`, feeBps: 30 } -const routerAbi = tradingContracts['trading/contracts/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter.abi +const routerAbi = tradingContracts['contracts/trading/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter.abi const migrateAbi = [ { type: 'function', diff --git a/trading/ui/ts/tests/live-workflow-safety.test.tsx b/ui/trading/ts/tests/features/live-workflow-safety.test.tsx similarity index 96% rename from trading/ui/ts/tests/live-workflow-safety.test.tsx rename to ui/trading/ts/tests/features/live-workflow-safety.test.tsx index 2a52d6182..6c84c6ad7 100644 --- a/trading/ui/ts/tests/live-workflow-safety.test.tsx +++ b/ui/trading/ts/tests/features/live-workflow-safety.test.tsx @@ -1,12 +1,14 @@ -import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { render } from 'preact' import { act } from 'preact/test-utils' import type { Address, Hash } from '@zoltar/shared/ethereum' -import { installDomEnvironment } from '../../../../ui/ts/tests/testUtils/domEnvironment.ts' -import type { DeploymentConfiguration } from '../protocol/config.ts' -import type { WalletSummaryState } from '../features/LiveTrading.tsx' -import type { LiveMarket } from '../protocol/live.ts' -import { renderIntoDocument } from './test-support/renderIntoDocument.tsx' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import type { DeploymentConfiguration } from '../../protocol/config.js' +import { LiveTrading as ProductionLiveTrading, liveLiquidityServices, liveSettlementServices, type WalletSummaryState } from '../../features/LiveTrading.js' +import { liveTradingControllerServices } from '../../features/liveTradingController.js' +import * as actualLive from '../../protocol/live.js' +import type { LiveMarket } from '../../protocol/live.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' const account = `0x${'11'.repeat(20)}` as Address const pool = `0x${'22'.repeat(20)}` as Address @@ -176,9 +178,8 @@ describe('live workflow safety boundary', () => { } return { ...(await discoverMarkets()), universeIds: [1n, 2n], selectedUniverseId: 1n } } - const actualLive = await import('../protocol/live.ts') - mock.module('../protocol/live.ts', () => ({ - ...actualLive, + const controllerServices = { + ...liveTradingControllerServices, createTradingPublicClient: () => ({}), validateLiveDeployment: async () => undefined, discoverLiveUniverseMarketPage: discoverSelectedUniverse, @@ -217,7 +218,6 @@ describe('live workflow safety boundary', () => { approved = true return transactionHash }, - approveLpRouter: async () => transactionHash, simulateEntry: async () => ({ blockNumber: 1n, amount: 10n ** 16n, @@ -244,8 +244,10 @@ describe('live workflow safety boundary', () => { return transactionHash }) }, - })) - const { LiveTrading } = await import('../features/LiveTrading.tsx') + } + const liquidityServices = { ...liveLiquidityServices, approveLpRouter: async () => transactionHash } + const settlementServices = { ...liveSettlementServices, approveRouter: controllerServices.approveRouter } + const LiveTrading = (props: Parameters[0]) => const workflowLocks: boolean[] = [] let rendered = await renderIntoDocument( workflowLocks.push(locked)} onWalletSummaryChange={recordWalletSummary} />) cleanupRendered = rendered.cleanup @@ -511,6 +513,8 @@ describe('live workflow safety boundary', () => { await act(async () => button('Enter').click()) await act(async () => button('Preview trade').click()) await settleAsyncWorkflow() + expect(button('Enter YES').classList.contains('tx-action-button')).toBeTrue() + expect(document.querySelector('.transaction-review-primary')).not.toBeNull() deferPositionBroadcast = true waitForPositionReceipt = true positionReceipt = deferred<{ status: 'success' | 'reverted' }>() @@ -528,6 +532,7 @@ describe('live workflow safety boundary', () => { await settleAsyncWorkflow() expect(document.body.textContent).toContain('Enter YES pending on-chain') expect(document.querySelector('.transaction-hash')?.textContent).toContain(transactionHash) + expect(document.querySelector('.transaction-hash-link')).not.toBeNull() positionReceipt.resolve({ status: 'success' }) await settleAsyncWorkflow() expect(document.body.textContent).toContain('Enter YES confirmed on-chain') @@ -548,7 +553,7 @@ describe('live workflow safety boundary', () => { expect(document.querySelector('.transaction-hash')).toBeNull() await act(async () => button('Exit').click()) - const positionAction = document.querySelector('.operation-block .primary-action') + const positionAction = document.querySelector('.operation-block .tx-action-button') const poolMechanics = document.querySelector('.operation-block .pool-mechanics') if (positionAction === null || poolMechanics === null) throw new Error('Missing position action or pool mechanics disclosure') expect(positionAction.compareDocumentPosition(poolMechanics) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0) @@ -558,7 +563,7 @@ describe('live workflow safety boundary', () => { await settleAsyncWorkflow() expect(document.body.textContent).toContain('Insured YES exit approval pending on-chain') expect(document.querySelector('.transaction-hash')?.textContent).toContain(transactionHash) - expect(button('Approve router for all outcome tokens').getAttribute('aria-busy')).toBe('true') + expect(button('Approving router…').getAttribute('aria-busy')).toBe('true') contextApprovalReceipt.resolve({ status: 'success' }) await settleAsyncWorkflow() expect(document.body.textContent).toContain('Insured YES exit approval confirmed on-chain') diff --git a/trading/ui/ts/tests/portfolio-scope.test.tsx b/ui/trading/ts/tests/features/portfolio-scope.test.tsx similarity index 96% rename from trading/ui/ts/tests/portfolio-scope.test.tsx rename to ui/trading/ts/tests/features/portfolio-scope.test.tsx index b5af9e812..b4f190c12 100644 --- a/trading/ui/ts/tests/portfolio-scope.test.tsx +++ b/ui/trading/ts/tests/features/portfolio-scope.test.tsx @@ -1,11 +1,11 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import type { Address } from '@zoltar/shared/ethereum' -import { installDomEnvironment } from '../../../../ui/ts/tests/testUtils/domEnvironment.ts' -import { LivePortfolio, LiveSecurityPoolDetails, PairInitializationAction, SecurityPoolRouteEmptyState } from '../features/LiveTrading.tsx' -import { Portfolio } from '../features/Routes.tsx' -import { demoMarket } from '../demo/markets.ts' -import type { LiveMarket } from '../protocol/live.ts' -import { renderIntoDocument } from './test-support/renderIntoDocument.tsx' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { LivePortfolio, LiveSecurityPoolDetails, PairInitializationAction, SecurityPoolRouteEmptyState } from '../../features/LiveTrading.js' +import { Portfolio } from '../../features/Routes.js' +import { demoMarket } from '../../demo/markets.js' +import type { LiveMarket } from '../../protocol/live.js' +import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' const pool: Address = `0x${'12'.repeat(20)}` const shareToken: Address = `0x${'34'.repeat(20)}` @@ -137,7 +137,7 @@ describe('live portfolio scope', () => { }) test('shows the deployed fee when an existing trading pool needs initialization', async () => { - const rendered = await renderIntoDocument() + const rendered = await renderIntoDocument() cleanupRendered = rendered.cleanup expect(rendered.container.textContent).toContain('needs initial liquidity') expect(rendered.container.textContent).toContain('Trading fee: 1.25%') diff --git a/trading/ui/ts/tests/ui-model.test.ts b/ui/trading/ts/tests/features/ui-model.test.ts similarity index 91% rename from trading/ui/ts/tests/ui-model.test.ts rename to ui/trading/ts/tests/features/ui-model.test.ts index b0a2025cd..d9239189d 100644 --- a/trading/ui/ts/tests/ui-model.test.ts +++ b/ui/trading/ts/tests/features/ui-model.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'bun:test' -import { bigintToSafeNumber, formatBpsMultiplier, formatCapacityOwnership, formatEthPerShare, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnits, parseUnitsOrUndefined } from '../app/format.ts' -import { demoAttoEthToAttoShares, demoAttoSharesToAttoEth, demoMarket, demoWalletBalances, lifecycleLabel, tradingClosedReason } from '../demo/markets.ts' -import { demoPreviewPresentation, quoteDemoEnterPosition, transactionMessage } from '../features/MarketDetail.tsx' +import { bigintToSafeNumber, formatBpsMultiplier, formatCapacityOwnership, formatEthPerShare, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnits, parseUnitsOrUndefined } from '../../lib/format.js' +import { demoAttoEthToAttoShares, demoAttoSharesToAttoEth, demoMarket, demoWalletBalances, lifecycleLabel, tradingClosedReason } from '../../demo/markets.js' +import { demoPreviewPresentation, quoteDemoEnterPosition, transactionMessage } from '../../features/MarketDetail.js' import { approvalFailureTransition, broadcastUncertainMessage, @@ -13,21 +13,24 @@ import { liquidityApprovalRequired, liquidityOperationAvailable, livePairInitialized, + marketRouteSubtitle, marketSelectionAfterDiscovery, migrationSimulationSummary, parseSlippageBps, parseTransactionValidityMinutes, positionControlsWorkflowLocked, + portfolioRouteSubtitle, securityPoolAddressFromRoute, settlementBalanceLabel, settlementInputBlocker, -} from '../features/LiveTrading.tsx' -import { liquidityActionAvailability, parseConditionalProbabilityBps, quoteDemoEthLiquidity, quoteDemoRemoval } from '../features/Routes.tsx' -import { roundedProbabilityLabels } from '../components/ProbabilityBar.tsx' +} from '../../features/LiveTrading.js' +import { liquidityActionAvailability, parseConditionalProbabilityBps, quoteDemoEthLiquidity, quoteDemoRemoval } from '../../features/Routes.js' +import { roundedProbabilityLabels } from '../../components/ProbabilityBar.js' import { collateMarketDiscoveryResults, createSecurityPoolDeploymentIndex, liveBalancesForMarket, + liveQuestionFields, marketAcceptsNewRisk, marketDiscoveryPage, marketDiscoveryRanges, @@ -39,16 +42,70 @@ import { retainApprovedMaximum, retainApprovedMinimum, refreshSecurityPoolDeploymentIndex, + registryBlockAnchorIsCanonical, + registrySnapshotBlockParameters, requireTransactionSlippageBps, requireTransactionValidityMinutes, selectUniverseDeployments, settlementAvailability, shareBalanceScope, type LiveMarket, -} from '../protocol/live.ts' -import { maximumInsuredExit } from '../../../ts/sdk/positions.ts' +} from '../../protocol/live.js' +import { maximumInsuredExit } from '@zoltar/shared/trading/positions' +import { initialQuestionClockTimestamp, questionClockShouldPollAgain } from '../../features/liveTradingController.js' describe('standalone trading UI model', () => { + test('uses the deterministic chain timestamp instead of the host wall clock in simulation', () => { + expect(initialQuestionClockTimestamp(1_735_689_600n, 1_800_000_000_000)).toBe(1_735_689_600n) + expect(initialQuestionClockTimestamp(undefined, 1_800_000_000_000)).toBe(1_800_000_000n) + }) + + test('stops polling the live chain clock at the exact question end boundary', () => { + expect(questionClockShouldPollAgain(undefined, 100n)).toBeTrue() + expect(questionClockShouldPollAgain(101n, 100n)).toBeTrue() + expect(questionClockShouldPollAgain(100n, 100n)).toBeFalse() + expect(questionClockShouldPollAgain(100n, 101n)).toBeFalse() + }) + + test('keeps the shared simulation banner as the only Browser Simulation disclosure', () => { + expect(marketRouteSubtitle('Browser Simulation', true)).toBe('Conditional prices only') + expect(marketRouteSubtitle('Ethereum Mainnet', false)).toBe('Ethereum Mainnet · conditional prices only') + expect(portfolioRouteSubtitle('Browser Simulation', true)).toBeUndefined() + expect(portfolioRouteSubtitle('Ethereum Mainnet', false)).toBe('Ethereum Mainnet') + }) + + test('validates a registry anchor at the current tip without requesting historical blocks', async () => { + const anchor = { blockNumber: 12n, blockHash: `0x${'12'.repeat(32)}` } + let historicalReads = 0 + expect( + await registryBlockAnchorIsCanonical( + anchor, + async () => anchor, + async () => { + historicalReads += 1 + return anchor + }, + ), + ).toBe(true) + expect(historicalReads).toBe(0) + }) + + test('accepts an older registry anchor when the deterministic simulator cannot read historical blocks', async () => { + const anchor = { blockNumber: 12n, blockHash: `0x${'12'.repeat(32)}` } + expect(await registryBlockAnchorIsCanonical(anchor, async () => ({ blockNumber: 13n, blockHash: `0x${'13'.repeat(32)}` }))).toBe(true) + }) + + test('uses latest registry reads only for the deterministic simulator', () => { + const anchor = { blockNumber: 12n, blockHash: `0x${'12'.repeat(32)}` } + expect(registrySnapshotBlockParameters(anchor, true)).toEqual({}) + expect(registrySnapshotBlockParameters(anchor, false)).toEqual({ blockHash: anchor.blockHash }) + }) + + test('reads question metadata from the ABI tuple preserved by the worker boundary', () => { + const question = ['Will this resolve?', 'Seeded market', 10n, 20n] as const + expect(liveQuestionFields(question)).toEqual({ description: 'Seeded market', endTime: 20n, title: 'Will this resolve?' }) + }) + test('keeps provider identifiers out of public error copy', () => { const pool = `0x${'12'.repeat(20)}` const shareToken = `0x${'34'.repeat(20)}` diff --git a/trading/ui/ts/tests/latestRequest.test.ts b/ui/trading/ts/tests/lib/latestRequest.test.ts similarity index 98% rename from trading/ui/ts/tests/latestRequest.test.ts rename to ui/trading/ts/tests/lib/latestRequest.test.ts index 30040bae0..25dd47f2f 100644 --- a/trading/ui/ts/tests/latestRequest.test.ts +++ b/ui/trading/ts/tests/lib/latestRequest.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'bun:test' -import { createExclusiveWorkflowGuard, createLatestRequestGuard } from '../app/latestRequest.ts' +import { createExclusiveWorkflowGuard, createLatestRequestGuard } from '../../lib/latestRequest.js' function deferred() { let resolvePromise: (value: T) => void = () => undefined diff --git a/trading/ui/ts/tests/config.test.ts b/ui/trading/ts/tests/protocol/config.test.ts similarity index 94% rename from trading/ui/ts/tests/config.test.ts rename to ui/trading/ts/tests/protocol/config.test.ts index 8d598c01c..10bf58a9a 100644 --- a/trading/ui/ts/tests/config.test.ts +++ b/ui/trading/ts/tests/protocol/config.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from 'bun:test' import { createPublicClient, custom, encodeAbiParameters, getAddress } from '@zoltar/shared/ethereum' -import { parseDeploymentSetupInput } from '../protocol/config.ts' -import { parseCoreDeployments } from '../protocol/coreDeployments.ts' -import { loadWalletHeaderBalances, validateRpcChainId } from '../protocol/live.ts' +import { parseDeploymentSetupInput } from '../../protocol/config.js' +import { parseCoreDeployments } from '../../protocol/coreDeployments.js' +import { loadWalletHeaderBalances, validateRpcChainId } from '../../protocol/live.js' const core = `0x${'34'.repeat(20)}` diff --git a/trading/ts/tests/coreDeploymentRegistry.test.ts b/ui/trading/ts/tests/protocol/coreDeploymentRegistry.test.ts similarity index 89% rename from trading/ts/tests/coreDeploymentRegistry.test.ts rename to ui/trading/ts/tests/protocol/coreDeploymentRegistry.test.ts index 2cd09e828..61bd2427e 100644 --- a/trading/ts/tests/coreDeploymentRegistry.test.ts +++ b/ui/trading/ts/tests/protocol/coreDeploymentRegistry.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from 'bun:test' import { getAddress } from '@zoltar/shared/ethereum' -import { coreDeploymentFromManifest } from '../../ui/build/core-deployments.mts' -import { defaultCoreDeploymentRpcUrls } from '../../ui/ts/protocol/coreDeploymentDefaults.ts' -import { parseCoreDeployments } from '../../ui/ts/protocol/coreDeployments.ts' +import { coreDeploymentFromManifest } from '../../../build/core-deployments.mts' +import { defaultCoreDeploymentRpcUrls } from '../../protocol/coreDeploymentDefaults.ts' +import { parseCoreDeployments } from '../../protocol/coreDeployments.ts' describe('trading core deployment registry', () => { test('copies the canonical deployment proxy and SecurityPoolFactory from a Zoltar manifest', () => { diff --git a/trading/ui/ts/tests/deployment-flow.test.ts b/ui/trading/ts/tests/protocol/deployment-flow.test.ts similarity index 99% rename from trading/ui/ts/tests/deployment-flow.test.ts rename to ui/trading/ts/tests/protocol/deployment-flow.test.ts index ca4de2d83..76bd8ed5e 100644 --- a/trading/ui/ts/tests/deployment-flow.test.ts +++ b/ui/trading/ts/tests/protocol/deployment-flow.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test' import { createPublicClient, custom, encodeAbiParameters, getAddress, type Address, type Hash } from '@zoltar/shared/ethereum' -import { CANONICAL_PROXY_DEPLOYER_RUNTIME_CODE, deployTradingStep, getTradingDeploymentPlan, loadTradingDeploymentStatus, nextTradingDeploymentStep } from '../protocol/deployment.ts' +import { CANONICAL_PROXY_DEPLOYER_RUNTIME_CODE, deployTradingStep, getTradingDeploymentPlan, loadTradingDeploymentStatus, nextTradingDeploymentStep } from '../../protocol/deployment.js' function examplePlan() { return getTradingDeploymentPlan( diff --git a/trading/ui/ts/tests/forks.test.ts b/ui/trading/ts/tests/protocol/forks.test.ts similarity index 99% rename from trading/ui/ts/tests/forks.test.ts rename to ui/trading/ts/tests/protocol/forks.test.ts index e0950004a..230bebd56 100644 --- a/trading/ui/ts/tests/forks.test.ts +++ b/ui/trading/ts/tests/protocol/forks.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test' import { createPublicClient, custom, encodeAbiParameters, getAddress, keccak256, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { getChildUniverseId, loadForkMigrationContext } from '../protocol/forks.ts' +import { getChildUniverseId, loadForkMigrationContext } from '../../protocol/forks.js' const pool = getAddress(`0x${'11'.repeat(20)}`) const shareToken = getAddress(`0x${'22'.repeat(20)}`) diff --git a/trading/ui/ts/tests/injected.test.ts b/ui/trading/ts/tests/protocol/injected.test.ts similarity index 97% rename from trading/ui/ts/tests/injected.test.ts rename to ui/trading/ts/tests/protocol/injected.test.ts index 8e1c4e6e8..a623a545c 100644 --- a/trading/ui/ts/tests/injected.test.ts +++ b/ui/trading/ts/tests/protocol/injected.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'bun:test' -import { createWalletContextSubscription, subscribeToWalletContextChanges } from '../protocol/injected.ts' +import { createWalletContextSubscription, subscribeToWalletContextChanges } from '../../protocol/injected.js' describe('injected wallet context events', () => { test('invalidates for account and chain changes, then removes both listeners', () => { diff --git a/trading/ui/ts/tests/live-guarded-writes.test.ts b/ui/trading/ts/tests/protocol/live-guarded-writes.test.ts similarity index 97% rename from trading/ui/ts/tests/live-guarded-writes.test.ts rename to ui/trading/ts/tests/protocol/live-guarded-writes.test.ts index 716ba50e3..28906d1fe 100644 --- a/trading/ui/ts/tests/live-guarded-writes.test.ts +++ b/ui/trading/ts/tests/protocol/live-guarded-writes.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from 'bun:test' import { createWalletClient, custom, decodeFunctionData, encodeAbiParameters, type Address, type Hex } from '@zoltar/shared/ethereum' -import type { DeploymentConfiguration } from '../protocol/config.ts' -import { simulateEntry, simulateExit, simulateLiquidity, submitFreshEntry, submitFreshExit, submitFreshLiquidity, type LiveMarket } from '../protocol/live.ts' -import { tradingContracts } from '../generated/contractArtifact.ts' +import type { DeploymentConfiguration } from '../../protocol/config.js' +import { simulateEntry, simulateExit, simulateLiquidity, submitFreshEntry, submitFreshExit, submitFreshLiquidity, type LiveMarket } from '../../protocol/live.js' +import { tradingContracts } from '../../generated/contractArtifact.js' const account = `0x${'11'.repeat(20)}` as Address const pool = `0x${'22'.repeat(20)}` as Address @@ -10,7 +10,7 @@ const pair = `0x${'33'.repeat(20)}` as Address const shareToken = `0x${'44'.repeat(20)}` as Address const blockHash = `0x${'55'.repeat(32)}` as Hex const transactionHash = `0x${'66'.repeat(32)}` as Hex -const routerAbi = tradingContracts['trading/contracts/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter.abi +const routerAbi = tradingContracts['contracts/trading/TwoWayConstantProductRouter.sol'].TwoWayConstantProductRouter.abi const configuration: DeploymentConfiguration = { chainId: 1, chainName: 'Test', rpcUrl: 'http://localhost', securityPoolFactory: `0x${'77'.repeat(20)}`, factory: `0x${'88'.repeat(20)}`, router: `0x${'99'.repeat(20)}`, feeBps: 30 } const market: LiveMarket = { pool, diff --git a/trading/ui/ts/tests/test-support/renderIntoDocument.tsx b/ui/trading/ts/tests/testUtils/renderIntoDocument.tsx similarity index 100% rename from trading/ui/ts/tests/test-support/renderIntoDocument.tsx rename to ui/trading/ts/tests/testUtils/renderIntoDocument.tsx diff --git a/ui/trading/tsconfig.json b/ui/trading/tsconfig.json new file mode 100644 index 000000000..7474427cd --- /dev/null +++ b/ui/trading/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@zoltar/shared/*": ["../../shared/js/*"], + "@zoltar/ui-core-shared/*": ["../coreShared/js/*"], + "@zoltar/ui-statoblast/*": ["../statoblast/js/*"], + "@zoltar/ui-zoltar/*": ["../zoltar/js/*"] + }, + "rootDir": "./ts", + "outDir": "./js" + }, + "include": ["./ts/**/*.ts", "./ts/**/*.tsx"], + "exclude": ["./ts/tests/**/*.ts", "./ts/tests/**/*.tsx"] +} diff --git a/ui/ts/app/components/AppHeaderShell.tsx b/ui/ts/app/components/AppHeaderShell.tsx deleted file mode 100644 index 6fdc34973..000000000 --- a/ui/ts/app/components/AppHeaderShell.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as appCopy from '../../copy/app.js' -import { OverviewPanels } from './OverviewPanels.js' -import { SimulationBanner } from '../../components/SimulationBanner.js' -import { TabNavigation } from '../../components/TabNavigation.js' -import type { OverviewPanelsProps, TabNavigationProps } from '../../features/types.js' -import type { SimulationController } from '../../simulation/controller.js' -import type { ComponentChildren } from 'preact' - -type AppHeaderShellProps = { - overview: OverviewPanelsProps - simulationController: SimulationController | undefined - subNavigation?: ComponentChildren - tabNavigation: TabNavigationProps - onEnvironmentChanged?: () => Promise - onRefresh: () => Promise -} - -export function AppHeaderShell({ overview, simulationController, subNavigation, tabNavigation, onEnvironmentChanged = async () => undefined, onRefresh }: AppHeaderShellProps) { - const focusAppContent = () => { - const appContent = document.getElementById('app-content') - if (appContent instanceof HTMLElement) appContent.focus() - } - - return ( - <> - - {simulationController === undefined ? undefined : } -
-
- -
-
- - {subNavigation} -
-
- - ) -} diff --git a/ui/ts/components/TabNavigation.tsx b/ui/ts/components/TabNavigation.tsx deleted file mode 100644 index 81c042e87..000000000 --- a/ui/ts/components/TabNavigation.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as appCopy from '../copy/app.js' -import * as commonCopy from '../copy/common.js' -import { ViewTabs } from './ViewTabs.js' -import { buildRouteHref, getTopLevelRouteSearch } from '../lib/routing.js' -import type { TabNavigationProps } from '../types/components.js' - -export function TabNavigation({ route, showDeployTab = true, augurStatoblastDeployed, deployRoute, marketRoute, openOracleRoute, securityPoolsRoute, onRouteChange }: TabNavigationProps) { - const disabledTabReason = appCopy.deploymentRequiredDetail - const options: Array<{ disabled?: boolean; href: string; label: string; reason?: string; value: Exclude }> = [] - if (showDeployTab || route === 'deploy' || route === 'not-found') options.push({ value: 'deploy', label: commonCopy.deploy, href: buildRouteHref(deployRoute, getTopLevelRouteSearch('deploy')) }) - options.push({ - value: 'zoltar', - label: commonCopy.zoltar, - href: buildRouteHref(marketRoute, getTopLevelRouteSearch('zoltar')), - disabled: !augurStatoblastDeployed, - ...(!augurStatoblastDeployed ? { reason: disabledTabReason } : {}), - }) - options.push({ - value: 'security-pools', - label: commonCopy.securityPools, - href: buildRouteHref(securityPoolsRoute, getTopLevelRouteSearch('security-pools')), - disabled: !augurStatoblastDeployed, - ...(!augurStatoblastDeployed ? { reason: disabledTabReason } : {}), - }) - options.push({ - value: 'open-oracle', - label: appCopy.oracleReports, - href: buildRouteHref(openOracleRoute, getTopLevelRouteSearch('open-oracle')), - disabled: !augurStatoblastDeployed, - ...(!augurStatoblastDeployed ? { reason: disabledTabReason } : {}), - }) - - return ( - - ) -} diff --git a/ui/ts/features/README.md b/ui/ts/features/README.md deleted file mode 100644 index 8fb465372..000000000 --- a/ui/ts/features/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# UI Features - -Feature directories own their route-specific components, hooks, and domain presentation logic. Keep code inside the feature that gives it meaning; move it to `ui/ts/components`, `ui/ts/hooks`, or `ui/ts/lib` only when it is genuinely reusable across features. - -Feature `lib` modules may own UI-agnostic calculations used only by that feature. If the protocol client also needs a calculation, place it in `ui/ts/protocol` or `ui/ts/lib`; protocol modules never import from feature directories. - -Protocol reads and writes belong in `ui/ts/protocol`. Application-shell composition belongs in `ui/ts/app`; features must not import it. Reusable presentational components and generic helpers belong in `ui/ts/components`, `ui/ts/hooks`, `ui/ts/lib`, and `ui/ts/types`; those shared layers must not depend on app or feature modules. Simulation may depend on protocol and shared modules, but not feature presentation code. - -Tests mirror feature names under `ui/ts/tests/features`, while app-shell tests live under `ui/ts/tests/app`. Feature-specific test support stays with its feature instead of the global `tests/testUtils` directory. Protocol and simulation tests may not reach upward into feature or app ownership. Browser-local simulation is an intentional top-level subsystem in `ui/ts/simulation`, with matching tests in `ui/ts/tests/simulation`. diff --git a/ui/ts/features/markets/components/MarketQuestionsSection.tsx b/ui/ts/features/markets/components/MarketQuestionsSection.tsx deleted file mode 100644 index c24169ece..000000000 --- a/ui/ts/features/markets/components/MarketQuestionsSection.tsx +++ /dev/null @@ -1,303 +0,0 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' -import { useEffect, useRef, useState } from 'preact/hooks' -import { EntityCard } from '../../../components/EntityCard.js' -import { Badge } from '../../../components/Badge.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { PaginationControls } from '../../../components/PaginationControls.js' -import { Question, getQuestionTitle } from './Question.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { StateHint } from '../../../components/StateHint.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { SecurityPoolLink } from '../../security-pools/components/SecurityPoolLink.js' -import { formatUniverseIdHex } from '../../universes/lib/universe.js' -import { getSecurityPoolStatusBadgeLabel } from '../../security-pools/lib/securityPoolLabels.js' -import { deriveSecurityPoolLifecycleState } from '../../security-pools/lib/securityPoolState.js' -import { sameCaseInsensitiveText } from '../../../lib/caseInsensitive.js' -import { zeroAddress } from '@zoltar/shared/ethereum' -import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, QUESTION_PAGE_SIZE } from '../../../lib/pagination.js' -import type { ListedSecurityPool, MarketDetailsPage } from '../../../types/contracts.js' - -function isCurrentQuestionPage(page: MarketDetailsPage | undefined, pageIndex: number, questionCount: bigint | undefined) { - return page?.pageIndex === pageIndex && page.pageSize === QUESTION_PAGE_SIZE && (questionCount === undefined || page.questionCount === questionCount) -} - -type MarketQuestionsSectionProps = { - activeUniverseId: bigint - environmentRefreshKey: number - hasForked: boolean - hasLoadedSecurityPools: boolean - loadingSecurityPools: boolean - loadingZoltarQuestionCount: boolean - loadingZoltarQuestions: boolean - onCreateQuestion: () => void - onLoadZoltarQuestions: () => Promise - onLoadZoltarQuestionPage: (pageIndex: number, pageSize: number) => Promise - onLoadSecurityPools: () => void - onOpenForkTab: () => void - onUseQuestionForFork: (questionId: string) => void - onUseQuestionForPool: (questionId: string) => void - securityPools: ListedSecurityPool[] - securityPoolsLoadError: string | undefined - zoltarQuestionCount: bigint | undefined - zoltarQuestionPage: MarketDetailsPage | undefined - zoltarQuestionsError: string | undefined -} -export function MarketQuestionsSection({ - activeUniverseId, - environmentRefreshKey, - hasForked, - hasLoadedSecurityPools, - loadingSecurityPools, - loadingZoltarQuestionCount, - loadingZoltarQuestions, - onCreateQuestion, - onLoadSecurityPools, - onLoadZoltarQuestions, - onLoadZoltarQuestionPage, - onOpenForkTab, - onUseQuestionForFork, - onUseQuestionForPool, - securityPools, - securityPoolsLoadError, - zoltarQuestionCount, - zoltarQuestionPage, - zoltarQuestionsError, -}: MarketQuestionsSectionProps) { - const noQuestionsAvailable = zoltarQuestionCount === 0n - const [pageIndex, setPageIndex] = useState(0) - const [activePageRequestKey, setActivePageRequestKey] = useState(undefined) - const [lastFailedPageRequestKey, setLastFailedPageRequestKey] = useState(undefined) - const lastSeenEnvironmentRefreshKeyRef = useRef(environmentRefreshKey) - const lastRequestedPageKeyRef = useRef(undefined) - const currentPageRequestKey = `${environmentRefreshKey}:${pageIndex}:${QUESTION_PAGE_SIZE}:${zoltarQuestionCount?.toString() ?? 'unknown'}` - const hasCurrentPageData = isCurrentQuestionPage(zoltarQuestionPage, pageIndex, zoltarQuestionCount) - const effectiveQuestionCount = zoltarQuestionPage?.questionCount ?? zoltarQuestionCount - const questionPageCount = getPaginationPageCount(effectiveQuestionCount, QUESTION_PAGE_SIZE) - const visibleQuestions = hasCurrentPageData && zoltarQuestionPage !== undefined ? zoltarQuestionPage.questions : [] - const isWaitingForPageData = activePageRequestKey === currentPageRequestKey - useEffect(() => { - setPageIndex(0) - }, [zoltarQuestionCount]) - useEffect(() => { - setLastFailedPageRequestKey(undefined) - lastRequestedPageKeyRef.current = undefined - }, [currentPageRequestKey]) - useEffect(() => { - if (loadingZoltarQuestionCount) return - if (zoltarQuestionCount === undefined || zoltarQuestionCount === 0n) return - const pageRequestKey = `${environmentRefreshKey}:${pageIndex}:${QUESTION_PAGE_SIZE}:${zoltarQuestionCount.toString()}` - const environmentChanged = lastSeenEnvironmentRefreshKeyRef.current !== environmentRefreshKey - const hasCurrentPageData = isCurrentQuestionPage(zoltarQuestionPage, pageIndex, zoltarQuestionCount) - if (hasCurrentPageData && !environmentChanged) { - if (lastFailedPageRequestKey === pageRequestKey) setLastFailedPageRequestKey(undefined) - if (activePageRequestKey === pageRequestKey) setActivePageRequestKey(undefined) - return - } - if (lastFailedPageRequestKey === pageRequestKey) return - if (activePageRequestKey === pageRequestKey) return - if (lastRequestedPageKeyRef.current === pageRequestKey) return - lastRequestedPageKeyRef.current = pageRequestKey - lastSeenEnvironmentRefreshKeyRef.current = environmentRefreshKey - setActivePageRequestKey(pageRequestKey) - void Promise.resolve(onLoadZoltarQuestionPage(pageIndex, QUESTION_PAGE_SIZE)) - .catch(() => { - setLastFailedPageRequestKey(current => (current === undefined ? pageRequestKey : current)) - }) - .finally(() => { - setActivePageRequestKey(current => (current === pageRequestKey ? undefined : current)) - }) - }, [activePageRequestKey, environmentRefreshKey, lastFailedPageRequestKey, loadingZoltarQuestionCount, onLoadZoltarQuestionPage, pageIndex, zoltarQuestionCount, zoltarQuestionPage]) - const hasPreviousPage = pageIndex > 0 - const hasNextPage = getHasNextPaginationPage(pageIndex, questionPageCount) - const retryQuestionLoad = () => { - if (loadingZoltarQuestionCount || loadingZoltarQuestions) return - if (zoltarQuestionCount === undefined) { - void onLoadZoltarQuestions().catch(() => undefined) - return - } - const pageRequestKey = currentPageRequestKey - lastRequestedPageKeyRef.current = pageRequestKey - setLastFailedPageRequestKey(undefined) - setActivePageRequestKey(pageRequestKey) - void onLoadZoltarQuestionPage(pageIndex, QUESTION_PAGE_SIZE) - .catch(() => { - setLastFailedPageRequestKey(current => (current === undefined ? pageRequestKey : current)) - }) - .finally(() => { - setActivePageRequestKey(current => (current === pageRequestKey ? undefined : current)) - }) - } - return ( - setPageIndex(current => current + 1)} - onPreviousPage={() => setPageIndex(current => Math.max(0, current - 1))} - summary={zoltarQuestionPage === undefined ? undefined : formatPaginationSummary(pageIndex, questionPageCount)} - /> - } - > - {zoltarQuestionsError === undefined ? undefined : ( - <> - -
- -
- - )} - {visibleQuestions.length === 0 ? ( - (() => { - if (loadingZoltarQuestionCount || loadingZoltarQuestions || isWaitingForPageData) - return ( -

- {marketCopy.loadingQuestions} -

- ) - if (noQuestionsAvailable) - return ( - - {commonCopy.createQuestionAction} - - } - /> - ) - if (zoltarQuestionsError === undefined && effectiveQuestionCount !== undefined && effectiveQuestionCount > 0n) return

{marketCopy.questionPageUnavailable}

- - return undefined - })() - ) : ( -
- {visibleQuestions.map(question => ( - - - {question.marketType === 'binary' ? undefined : {marketCopy.binaryPoolsOnly}} - -
- } - > - - {(() => { - const linkedPools = securityPools.filter(pool => sameCaseInsensitiveText(pool.questionId, question.questionId)) - const linkedPoolsContent = (() => { - if (!hasLoadedSecurityPools) { - if (securityPoolsLoadError === undefined) - return ( -

- {marketCopy.loadingLinkedPools} -

- ) - return ( -
-

- {securityPoolsLoadError} -

- -
- ) - } - if (linkedPools.length === 0) return

{marketCopy.noLinkedPool}

- return ( -
- {linkedPools.map(pool => { - const lifecycleState = deriveSecurityPoolLifecycleState({ - hasForkActivity: pool.hasForkActivity, - isChildPool: pool.parent !== zeroAddress, - questionOutcome: pool.questionOutcome, - systemState: pool.systemState, - universeHasForked: pool.universeHasForked, - }) - const statusLabel = getSecurityPoolStatusBadgeLabel({ hasForkActivity: pool.hasForkActivity, lifecycleState, questionOutcome: pool.questionOutcome }) - return ( -
- {pool.universeId === activeUniverseId ? undefined : ( - - {marketCopy.universeMismatch} -

{marketCopy.formatLinkedPoolUniverseMismatch(formatUniverseIdHex(pool.universeId), formatUniverseIdHex(activeUniverseId))}

-
- )} -
- {statusLabel} - - {marketCopy.openInterest}: - - - {marketCopy.shareSupply}: - -
-
- {marketCopy.completeSetOperations} -
-
- - {marketCopy.openSharesAndPosition} - - - {marketCopy.openReporting} - -
-
- ) - })} -
- ) - })() - return ( -
-
- {marketCopy.linkedPools} -
- {linkedPoolsContent} -
- ) - })()} - - ))} -
- )} - - ) -} diff --git a/ui/ts/features/markets/components/MarketSection.tsx b/ui/ts/features/markets/components/MarketSection.tsx deleted file mode 100644 index 51a5bae49..000000000 --- a/ui/ts/features/markets/components/MarketSection.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import * as commonCopy from '../../../copy/common.js' -import * as marketCopy from '../../../copy/market.js' -import * as zoltarCopy from '../../../copy/zoltar.js' -import { useEffect, useRef, useState } from 'preact/hooks' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { IdentifierValue } from '../../../components/IdentifierValue.js' -import { MetricField } from '../../../components/MetricField.js' -import { ForkZoltarSection } from '../../universes/components/ForkZoltarSection.js' -import { MarketCreateQuestionSection } from './MarketCreateQuestionSection.js' -import { MarketOverviewSection } from './MarketOverviewSection.js' -import { MarketQuestionsSection } from './MarketQuestionsSection.js' -import { Question } from './Question.js' -import { ActionLauncherButton } from '../../../components/ActionLauncherButton.js' -import { OperationModal } from '../../../components/OperationModal.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { ZoltarMigrationSection } from '../../universes/components/ZoltarMigrationSection.js' -import { isActiveAppChain } from '../../../lib/network.js' -import { normalizeQuestionId } from '../../../lib/questionId.js' -import { getMarketTypeLabel } from '../lib/marketType.js' -import type { MarketSectionProps } from '../../types.js' - -export function MarketSection({ - accountState, - activeUniverseId, - activeView, - environmentRefreshKey, - securityPools = [], - hasLoadedSecurityPools, - hasLoadedZoltarQuestions, - loadingZoltarForkAccess, - zoltarForkActiveAction, - loadingZoltarQuestionCount, - loadingZoltarQuestion, - loadingZoltarQuestions, - loadingZoltarUniverse, - loadingSecurityPools, - marketForm, - marketCreating, - marketError, - marketResult, - onActiveViewChange, - onApproveZoltarForkRep, - onCreateChildUniverseForOutcomeIndex, - onCreateMarket, - onForkZoltar, - onLoadZoltarQuestions, - onLoadZoltarQuestion, - onLoadZoltarQuestionPage, - onLoadSecurityPools, - onMarketFormChange, - onMigrateInternalRep, - onPrepareRepForMigration, - onResetMarket, - onUseQuestionForFork, - onUseQuestionForPool, - onZoltarForkQuestionIdChange, - onZoltarMigrationFormChange, - zoltarChildUniverseError, - zoltarChildUniversePendingOutcomeIndex, - zoltarForkApproval, - zoltarForkError, - zoltarForkPending, - zoltarForkQuestionId, - zoltarForkRepBalanceAttoRep, - zoltarMigrationChildRepBalancesAttoRep, - zoltarMigrationActiveAction, - zoltarMigrationError, - zoltarMigrationForm, - zoltarMigrationPending, - zoltarMigrationPreparedRepBalanceAttoRep, - zoltarQuestionCount, - zoltarQuestionLookupError, - zoltarQuestionLookupId, - zoltarQuestionPage, - zoltarQuestions, - zoltarQuestionsError, - zoltarUniverse, - zoltarUniverseState, - securityPoolsLoadError, -}: MarketSectionProps) { - const hasForked = zoltarUniverse?.hasForked === true - const isOnActiveAppChain = isActiveAppChain(accountState.chainId) - const view = activeView - const showUniverseSummary = view === 'questions' && zoltarUniverse !== undefined - const [forkModalOpen, setForkModalOpen] = useState(false) - const requestedForkQuestionId = useRef(undefined) - const localForkQuestionId = zoltarForkQuestionId.trim() - const canonicalForkQuestion = zoltarUniverse?.forkQuestionDetails - const selectedForkQuestionId = hasForked ? canonicalForkQuestion?.questionId : localForkQuestionId || canonicalForkQuestion?.questionId - const normalizedSelectedForkQuestionId = selectedForkQuestionId === undefined ? undefined : normalizeQuestionId(selectedForkQuestionId) - const selectedForkQuestion = - normalizedSelectedForkQuestionId === undefined - ? undefined - : (zoltarQuestions.find(question => normalizeQuestionId(question.questionId) === normalizedSelectedForkQuestionId) ?? (canonicalForkQuestion !== undefined && normalizeQuestionId(canonicalForkQuestion.questionId) === normalizedSelectedForkQuestionId ? canonicalForkQuestion : undefined)) - const isSelectedForkQuestionLookup = normalizedSelectedForkQuestionId !== undefined && zoltarQuestionLookupId === normalizedSelectedForkQuestionId - const forkQuestionLookupLoading = selectedForkQuestion === undefined && (loadingZoltarQuestions || (isSelectedForkQuestionLookup && loadingZoltarQuestion)) - const forkQuestionMetadataFallback = forkQuestionLookupLoading ? commonCopy.loadingWithEllipsis : commonCopy.unavailable - const forkModalTitle = hasForked ? zoltarCopy.viewForkDetailsTitle : zoltarCopy.forkZoltar - const permanentRepBurn = zoltarUniverse?.forkBurnDivisor === undefined || zoltarUniverse.forkBurnDivisor <= 1n ? undefined : zoltarUniverse.forkThresholdAttoRep / zoltarUniverse.forkBurnDivisor - const forkContext = [{ label: commonCopy.question, value: selectedForkQuestion?.title ?? selectedForkQuestionId ?? commonCopy.noneSelected }] - - useEffect(() => { - if (view !== 'migrate') return - if (zoltarUniverse === undefined) return - if (hasForked) return - onActiveViewChange('questions') - }, [hasForked, onActiveViewChange, view, zoltarUniverse]) - - useEffect(() => { - if (!forkModalOpen) { - requestedForkQuestionId.current = undefined - return - } - if (hasForked || selectedForkQuestion !== undefined || normalizedSelectedForkQuestionId === undefined) return - if (requestedForkQuestionId.current === normalizedSelectedForkQuestionId) return - const timeoutId = window.setTimeout(() => { - requestedForkQuestionId.current = normalizedSelectedForkQuestionId - void onLoadZoltarQuestion(normalizedSelectedForkQuestionId) - }, 250) - return () => window.clearTimeout(timeoutId) - }, [forkModalOpen, hasForked, normalizedSelectedForkQuestionId, onLoadZoltarQuestion, selectedForkQuestion]) - - return ( -
- - {showUniverseSummary ? ( - - ) : ( - -
-

{commonCopy.status}

- {hasForked ? commonCopy.forked : marketCopy.unforked} -
-
-

{marketCopy.questions}

- {zoltarQuestionCount?.toString() ?? commonCopy.metricUnavailablePlaceholder} -
-
- )} -
-
- {view === 'questions' ? ( - <> - {hasForked ? ( - -
- -
-
- ) : undefined} - onActiveViewChange('create')} - onLoadZoltarQuestions={onLoadZoltarQuestions} - onLoadZoltarQuestionPage={onLoadZoltarQuestionPage} - loadingZoltarQuestionCount={loadingZoltarQuestionCount} - loadingZoltarQuestions={loadingZoltarQuestions} - loadingSecurityPools={loadingSecurityPools} - hasLoadedSecurityPools={hasLoadedSecurityPools} - onLoadSecurityPools={onLoadSecurityPools} - onOpenForkTab={() => onActiveViewChange('fork')} - onUseQuestionForFork={onUseQuestionForFork} - onUseQuestionForPool={onUseQuestionForPool} - zoltarQuestionCount={zoltarQuestionCount} - zoltarQuestionPage={zoltarQuestionPage} - zoltarQuestionsError={zoltarQuestionsError} - securityPools={securityPools} - securityPoolsLoadError={securityPoolsLoadError} - /> - - ) : undefined} - - {view === 'create' ? ( - onActiveViewChange('fork')} - onResetMarket={onResetMarket} - onUseQuestionForFork={onUseQuestionForFork} - onUseQuestionForPool={onUseQuestionForPool} - zoltarQuestions={zoltarQuestions} - /> - ) : undefined} - - {view === 'fork' ? ( - <> - - {hasForked ? undefined : ( - - - - - - - - - )} -
- setForkModalOpen(true)} pending={false} pendingLabel={forkModalTitle} /> -
- {selectedForkQuestionId === undefined ? undefined : ( -
-

{marketCopy.selectedForkQuestionSummary}

- {selectedForkQuestion === undefined ? ( - -
-

{marketCopy.title}

- {forkQuestionMetadataFallback} -
-
-

{marketCopy.outcomes}

- {forkQuestionMetadataFallback} -
-
-

{marketCopy.questionType}

- {forkQuestionMetadataFallback} -
-
-

{commonCopy.questionId}

- -
-
- ) : ( - <> - - -
-

{marketCopy.questionType}

- {getMarketTypeLabel(selectedForkQuestion.marketType)} -
-
- - )} -
- )} -
- setForkModalOpen(false)} title={forkModalTitle}> - { - if (normalizedSelectedForkQuestionId === undefined) return - requestedForkQuestionId.current = normalizedSelectedForkQuestionId - void onLoadZoltarQuestion(normalizedSelectedForkQuestionId) - }} - onZoltarForkQuestionIdChange={onZoltarForkQuestionIdChange} - zoltarForkActiveAction={zoltarForkActiveAction} - zoltarForkApproval={zoltarForkApproval} - zoltarForkError={zoltarForkError} - zoltarForkPending={zoltarForkPending} - zoltarForkQuestionId={selectedForkQuestionId ?? ''} - zoltarForkRepBalanceAttoRep={zoltarForkRepBalanceAttoRep} - zoltarQuestionLookupError={isSelectedForkQuestionLookup ? zoltarQuestionLookupError : undefined} - zoltarQuestionLookupId={zoltarQuestionLookupId} - zoltarQuestions={zoltarQuestions} - zoltarUniverse={zoltarUniverse} - zoltarUniverseState={zoltarUniverseState} - /> - - - ) : undefined} - - {view === 'migrate' ? ( - - ) : undefined} -
-
- ) -} diff --git a/ui/ts/features/markets/lib/marketForm.ts b/ui/ts/features/markets/lib/marketForm.ts deleted file mode 100644 index d2ce058b6..000000000 --- a/ui/ts/features/markets/lib/marketForm.ts +++ /dev/null @@ -1,196 +0,0 @@ -import type { ForkAuctionFormState, MarketFormState, OpenOracleCreateFormState, OpenOracleFormState, ReportingFormState, ReportingWithdrawDepositIndexesByOutcome, SecurityPoolFormState, SecurityVaultFormState, TradingFormState, ZoltarMigrationFormState } from '../../../types/app.js' -import { DEFAULT_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS } from '@zoltar/shared/oracleInitialReport' -import { parseDecimalInput, tryParseDecimalInput } from '../../../lib/decimal.js' -import { parseBigIntInput, tryParseBigIntInput } from '../../../lib/integerInput.js' -import { formatCurrencyInputBalance } from '../../../lib/formatters.js' - -export { parseBigIntInput, tryParseBigIntInput } - -const DEFAULT_OPEN_ORACLE_DISPUTE_DELAY_SECONDS = '3600' -const DEFAULT_OPEN_ORACLE_SETTLEMENT_DELAY_SECONDS = '86400' -const STATOBLAST_SECURITY_MULTIPLIER_DECIMALS = 4 - -export function getDefaultMarketFormState(): MarketFormState { - return { - answerUnit: '', - categoricalOutcomes: ['Yes', 'No'], - description: '', - endTime: '', - marketType: 'binary', - scalarIncrement: '1', - scalarMax: '100', - scalarMin: '0', - title: '', - startTime: '', - } -} - -export function getDefaultSecurityPoolFormState(): SecurityPoolFormState { - return { - initialReportPriorityFeeGwei: formatCurrencyInputBalance(DEFAULT_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS, 9), - marketId: '', - statoblastSecurityMultiplierBps: '2', - } -} - -export function parseStatoblastSecurityMultiplierBpsInput(value: string) { - return parseDecimalInput(value, 'Statoblast security multiplier', STATOBLAST_SECURITY_MULTIPLIER_DECIMALS) -} - -export function tryParseStatoblastSecurityMultiplierBpsInput(value: string) { - return tryParseDecimalInput(value, STATOBLAST_SECURITY_MULTIPLIER_DECIMALS) -} - -export function getDefaultSecurityVaultFormState(): SecurityVaultFormState { - return { - depositAmount: '0', - targetHealthFactor: '2', - repWithdrawAmount: '0', - selectedVaultOwner: '', - securityPoolAddress: '', - stagedOperationTimeoutMinutes: '5', - } -} - -export function getDefaultOpenOracleFormState(): OpenOracleFormState { - return { - amount1: '0', - amount2: '0', - disputeNewAmount1: '0', - disputeNewAmount2: '0', - disputeTokenToSwap: 'token1', - reportId: '', - stateHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - } -} - -export function getDefaultOpenOracleCreateFormState(): OpenOracleCreateFormState { - return { - disputeDelay: DEFAULT_OPEN_ORACLE_DISPUTE_DELAY_SECONDS, - escalationHalt: '0', - exactToken1Report: '0', - initialToken2Amount: '0', - ethValue: '0', - feePercentage: '0', - multiplier: '100', - protocolFee: '0', - settlementTime: DEFAULT_OPEN_ORACLE_SETTLEMENT_DELAY_SECONDS, - settlerRewardEthAmount: '0', - token1Address: '', - token2Address: '', - } -} - -export function getDefaultTradingFormState(): TradingFormState { - return { - completeSetAmount: '0', - redeemAmount: '0', - securityPoolAddress: '', - selectedShareOutcome: 'yes', - targetOutcomeIndexes: '', - } -} - -export function getDefaultReportingWithdrawDepositIndexesByOutcome(): ReportingWithdrawDepositIndexesByOutcome { - return { - invalid: [], - yes: [], - no: [], - } -} - -export function getDefaultReportingFormState(): ReportingFormState { - return { - reportAmount: '0', - securityPoolAddress: '', - selectedOutcome: undefined, - selectedWithdrawDepositIndexesByOutcome: getDefaultReportingWithdrawDepositIndexesByOutcome(), - } -} - -export function getDefaultForkAuctionFormState(): ForkAuctionFormState { - return { - claimBidIndex: '0', - claimBidTick: '0', - depositIndexes: '', - directForkQuestionId: '', - directForkUniverseId: '0', - refundBidIndex: '0', - refundTick: '0', - repMigrationOutcomes: 'yes', - securityPoolAddress: '', - selectedOutcome: 'yes', - settlementAddress: '', - submitBidAmount: '0', - submitBidPrice: '0', - vaultAddress: '', - } -} - -export function getDefaultZoltarMigrationFormState(): ZoltarMigrationFormState { - return { - amount: '0.0', - outcomeIndexes: '', - } -} - -export function parseRepAmountInput(value: string, label: string) { - return parseDecimalInput(value, label, 18) -} - -export function parseEthAmountInput(value: string, label: string) { - return parseDecimalInput(value, label, 18) -} - -export function tryParseRepAmountInput(value: string) { - return tryParseDecimalInput(value, 18) -} - -export function tryParseEthAmountInput(value: string) { - return tryParseDecimalInput(value, 18) -} - -export function parseOptionalRepAmountInput(value: string) { - const trimmed = value.trim() - if (trimmed === '') return undefined - return tryParseRepAmountInput(trimmed) -} - -export function parseTradingAmountInput(value: string, label: string) { - return parseDecimalInput(value, label, 18) -} - -export function tryParseTradingAmountInput(value: string) { - return tryParseDecimalInput(value, 18) -} - -export function parseTruthAuctionPriceInput(value: string, label: string) { - return parseDecimalInput(value, label, 18) -} - -export function tryParseTruthAuctionPriceInput(value: string) { - return tryParseDecimalInput(value, 18) -} - -export function parseTruthAuctionAmountInput(value: string, label: string) { - return parseDecimalInput(value, label, 18) -} - -export function tryParseTruthAuctionAmountInput(value: string) { - return tryParseDecimalInput(value, 18) -} - -export function tryParseTimestampInput(value: string) { - const trimmed = value.trim() - if (/^-?\d+$/.test(trimmed)) return BigInt(trimmed) - const timestampMs = new Date(value).getTime() - if (Number.isNaN(timestampMs)) return undefined - return BigInt(Math.floor(timestampMs / 1000)) -} - -export function parseTimestampInput(value: string, label: string) { - const timestamp = tryParseTimestampInput(value) - if (timestamp === undefined) throw new Error(`${label} is invalid`) - if (timestamp < 0n) throw new Error(`${label} must not be before the Unix epoch`) - return timestamp -} diff --git a/ui/ts/features/reporting/lib/reporting.ts b/ui/ts/features/reporting/lib/reporting.ts deleted file mode 100644 index 7615fcb23..000000000 --- a/ui/ts/features/reporting/lib/reporting.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { ReportingOutcomeKey } from '../../../types/contracts.js' -import { assertNever } from '../../../lib/assert.js' -import { formatTimestampWithRelative } from '../../../lib/formatters.js' - -const REPORTING_OUTCOME_OPTIONS: { key: ReportingOutcomeKey; label: string }[] = [ - { key: 'invalid', label: 'Invalid' }, - { key: 'yes', label: 'Yes' }, - { key: 'no', label: 'No' }, -] - -export const REPORTING_OUTCOME_DROPDOWN_OPTIONS = REPORTING_OUTCOME_OPTIONS.map(option => ({ - value: option.key, - label: option.label, -})) - -export function getReportingOutcomeLabel(outcome: ReportingOutcomeKey | 'none') { - switch (outcome) { - case 'invalid': - return 'Invalid' - case 'yes': - return 'Yes' - case 'no': - return 'No' - case 'none': - return 'Unresolved' - default: - return assertNever(outcome) - } -} - -export function getReportingLockedUntilMessage(endTime: bigint, currentTimestamp: bigint | undefined) { - return `Reporting opens when this pool's underlying question ends: ${formatTimestampWithRelative(endTime, currentTimestamp)}.` -} - -export function hasReportingOpened(endTime: bigint, currentTimestamp: bigint | undefined) { - if (currentTimestamp === undefined) return undefined - return currentTimestamp > endTime -} diff --git a/ui/ts/features/transactionPresentations.tsx b/ui/ts/features/transactionPresentations.tsx deleted file mode 100644 index b7cb08822..000000000 --- a/ui/ts/features/transactionPresentations.tsx +++ /dev/null @@ -1,617 +0,0 @@ -import * as commonCopy from '../copy/common.js' -import * as transactionCopy from '../copy/transaction.js' -import * as marketCopy from '../copy/market.js' -import * as openOracleCopy from '../copy/openOracle.js' -import * as securityPoolCopy from '../copy/securityPool.js' -import type { Hash } from '@zoltar/shared/ethereum' -import { AddressValue } from '../components/AddressValue.js' -import { IdentifierValue } from '../components/IdentifierValue.js' -import { formatCurrencyBalanceWithUnit, formatValueWithUnit } from '../lib/formatters.js' -import { AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL } from './truth-auctions/lib/forkAuction.js' -import { getReportingOutcomeLabel } from './reporting/lib/reporting.js' -import { getMarketTypeLabel } from './markets/lib/marketType.js' -import { formatStatoblastSecurityMultiplier } from './markets/lib/trading.js' -import { buildIntent, buildPresentation, withWarning } from '../lib/transactionPresentations.js' -import type { TransactionIntent } from '../types/components.js' -import type { - ForkAuctionActionResult, - MarketCreationResult, - OpenOracleActionResult, - ReportingActionResult, - SecurityPoolCreationResult, - SecurityPoolOverviewActionResult, - SecurityVaultActionResult, - TradingActionResult, - ZoltarChildUniverseActionResult, - ZoltarForkActionResult, - ZoltarMigrationActionResult, -} from '../types/contracts.js' -function humanizeAction(action: string) { - return action - .replace(/([A-Z])/g, ' $1') - .replace(/^./, value => value.toUpperCase()) - .replaceAll(/\bRep\b/g, commonCopy.rep) - .replaceAll(/\bEth\b/g, commonCopy.eth) - .replaceAll(/\bWeth\b/g, commonCopy.weth) -} - -export function createDeploymentTransactionIntent(stepLabel: string) { - return buildIntent({ - action: 'deploy', - source: 'deployment', - submittedTitle: transactionCopy.formatDeployingValue(stepLabel), - }) -} - -export function createDeploymentSuccessPresentation(stepLabel: string, hash: Hash) { - return buildPresentation({ - hash, - title: transactionCopy.formatValueDeployed(stepLabel), - tone: 'success', - }) -} - -type MarketCreationTransactionContext = { - marketType: MarketCreationResult['marketType'] - title?: string | undefined - universeId?: bigint | undefined -} - -function getMarketCreationTransactionRows(context: MarketCreationTransactionContext) { - return [...(context.title === undefined || context.title.trim() === '' ? [] : [{ label: marketCopy.title, value: context.title.trim() }]), { label: marketCopy.questionType, value: getMarketTypeLabel(context.marketType) }] -} - -export function createMarketCreationTransactionIntent(context: MarketCreationTransactionContext) { - return buildIntent({ - action: 'createMarket', - rows: getMarketCreationTransactionRows(context), - source: 'zoltar', - submittedTitle: transactionCopy.creatingQuestion, - universeId: context.universeId, - }) -} - -export function createMarketCreationSuccessPresentation(result: MarketCreationResult, context?: Omit) { - return buildPresentation({ - hash: result.createQuestionHash, - rows: [{ label: commonCopy.questionId, value: }, ...getMarketCreationTransactionRows({ ...context, marketType: result.marketType })], - title: transactionCopy.questionCreated, - tone: 'success', - universeId: context?.universeId, - }) -} - -export function createMarketCreationWarningPresentation(result: MarketCreationResult, message: string, context?: Omit) { - return withWarning(createMarketCreationSuccessPresentation(result, context), message) -} - -type QuestionUniverseTransactionContext = { - questionId?: string | undefined - universeId?: bigint | undefined -} - -function getQuestionUniverseTransactionRows(context: QuestionUniverseTransactionContext | undefined) { - if (context === undefined) return undefined - return [...(context.questionId === undefined || context.questionId.trim() === '' ? [] : [{ label: commonCopy.questionId, value: }])] -} - -export function createZoltarForkTransactionIntent(actionName: 'approve' | 'fork', context?: QuestionUniverseTransactionContext) { - return buildIntent({ - action: actionName, - rows: getQuestionUniverseTransactionRows(context), - source: 'zoltar', - submittedTitle: actionName === 'approve' ? transactionCopy.approvingForkRep : transactionCopy.forkingZoltar, - universeId: context?.universeId, - }) -} - -export function createZoltarForkSuccessPresentation(result: ZoltarForkActionResult) { - const title = result.action === 'approveForkRep' ? transactionCopy.forkRepApproved : transactionCopy.zoltarForkSubmitted - return buildPresentation({ - hash: result.hash, - rows: [{ label: commonCopy.questionId, value: }], - title, - tone: 'success', - universeId: result.universeId, - }) -} - -export function createZoltarForkWarningPresentation(result: ZoltarForkActionResult, message: string) { - return withWarning(createZoltarForkSuccessPresentation(result), message) -} - -type ChildUniverseTransactionContext = { - outcomeIndex?: bigint | undefined - universeId?: bigint | undefined -} - -function getChildUniverseTransactionRows(context: ChildUniverseTransactionContext | undefined) { - if (context === undefined) return undefined - return context.outcomeIndex === undefined ? [] : [{ label: commonCopy.outcomeIndex, value: context.outcomeIndex.toString() }] -} - -export function createChildUniverseTransactionIntent(source: 'fork-auction' | 'zoltar', context?: ChildUniverseTransactionContext) { - return buildIntent({ - action: 'createChildUniverse', - rows: getChildUniverseTransactionRows(context), - source, - submittedTitle: transactionCopy.deployingChildUniverse, - universeId: context?.universeId, - }) -} - -export function createChildUniverseSuccessPresentation(result: ZoltarChildUniverseActionResult) { - return buildPresentation({ - hash: result.hash, - rows: [{ label: commonCopy.outcomeIndex, value: result.outcomeIndex.toString() }], - title: transactionCopy.childUniverseDeployed, - tone: 'success', - universeId: result.universeId, - }) -} - -export function createChildUniverseWarningPresentation(result: ZoltarChildUniverseActionResult, message: string) { - return withWarning(createChildUniverseSuccessPresentation(result), message) -} - -type ZoltarMigrationTransactionContext = { - amount?: string | undefined - outcomeIndexes?: string | undefined - universeId?: bigint | undefined -} - -function getZoltarMigrationTransactionRows(context: ZoltarMigrationTransactionContext | undefined) { - if (context === undefined) return undefined - return [ - ...(context.amount === undefined || context.amount.trim() === '' ? [] : [{ label: commonCopy.amount, value: formatValueWithUnit(context.amount.trim(), commonCopy.rep) }]), - ...(context.outcomeIndexes === undefined || context.outcomeIndexes.trim() === '' ? [] : [{ label: transactionCopy.outcomeIndexes, value: context.outcomeIndexes.trim() }]), - ] -} - -export function createZoltarMigrationTransactionIntent(actionName: 'prepare' | 'split', context?: ZoltarMigrationTransactionContext) { - return buildIntent({ - action: actionName, - rows: getZoltarMigrationTransactionRows(context), - source: 'zoltar', - submittedTitle: actionName === 'prepare' ? transactionCopy.preparingRep : transactionCopy.splittingRep, - universeId: context?.universeId, - }) -} - -export function createZoltarMigrationSuccessPresentation(result: ZoltarMigrationActionResult) { - return buildPresentation({ - detail: result.action === 'addRepToMigrationBalance' ? transactionCopy.migrationRepPreparationSuccessDetail : transactionCopy.repSplitSuccessDetail, - hash: result.hash, - rows: [ - { label: commonCopy.amount, value: formatCurrencyBalanceWithUnit(result.amountAttoRep, commonCopy.rep) }, - { label: transactionCopy.outcomeIndexes, value: result.outcomeIndexes.length === 0 ? commonCopy.none : result.outcomeIndexes.join(', ') }, - ], - title: result.action === 'addRepToMigrationBalance' ? transactionCopy.repPrepared : transactionCopy.repSplit, - tone: 'success', - universeId: result.universeId, - }) -} - -export function createZoltarMigrationWarningPresentation(result: ZoltarMigrationActionResult, message: string) { - return withWarning(createZoltarMigrationSuccessPresentation(result), message) -} - -type SecurityPoolCreationTransactionContext = { - initialReportPriorityFeeGwei?: string | undefined - questionId?: string | undefined - statoblastSecurityMultiplierBps?: bigint | undefined - universeId?: bigint | undefined -} - -function getSecurityPoolCreationTransactionRows(context: SecurityPoolCreationTransactionContext | undefined) { - if (context === undefined) return undefined - return [ - ...(context.initialReportPriorityFeeGwei === undefined || context.initialReportPriorityFeeGwei.trim() === '' ? [] : [{ label: commonCopy.initialReportPriorityFee, value: formatValueWithUnit(context.initialReportPriorityFeeGwei.trim(), commonCopy.gwei) }]), - ...(context.questionId === undefined || context.questionId.trim() === '' ? [] : [{ label: commonCopy.questionId, value: }]), - ...(context.statoblastSecurityMultiplierBps === undefined ? [] : [{ label: commonCopy.statoblastSecurityMultiplierBps, value: `${formatStatoblastSecurityMultiplier(context.statoblastSecurityMultiplierBps)}x` }]), - ] -} - -export function createSecurityPoolCreationTransactionIntent(context?: SecurityPoolCreationTransactionContext) { - return buildIntent({ - action: 'createSecurityPool', - rows: getSecurityPoolCreationTransactionRows(context), - source: 'security-pools', - submittedTitle: transactionCopy.creatingSecurityPool, - universeId: context?.universeId, - }) -} - -export function createSecurityPoolCreationSuccessPresentation(result: SecurityPoolCreationResult) { - return buildPresentation({ - detail: transactionCopy.securityPoolCreatedDetail, - hash: result.deployPoolHash, - rows: [ - { label: transactionCopy.pool, value: }, - { label: commonCopy.questionId, value: }, - { label: commonCopy.statoblastSecurityMultiplierBps, value: `${formatStatoblastSecurityMultiplier(result.statoblastSecurityMultiplierBps)}x` }, - { label: commonCopy.initialReportPriorityFee, value: formatCurrencyBalanceWithUnit(result.initialReportPriorityFeeAttoEthPerGas, commonCopy.gwei, 9) }, - ], - title: transactionCopy.securityPoolCreated, - tone: 'success', - universeId: result.universeId, - }) -} - -export function createSecurityPoolCreationWarningPresentation(result: SecurityPoolCreationResult, message: string) { - return withWarning(createSecurityPoolCreationSuccessPresentation(result), message) -} - -type SecurityVaultTransactionContext = { - securityPoolAddress?: string | undefined - universeId?: bigint | undefined - vaultAddress?: string | undefined -} - -function getSecurityVaultTransactionRows(context: SecurityVaultTransactionContext | undefined) { - if (context === undefined) return undefined - return [ - ...(context.securityPoolAddress === undefined || context.securityPoolAddress.trim() === '' ? [] : [{ label: commonCopy.securityPoolAddress, value: }]), - ...(context.vaultAddress === undefined || context.vaultAddress.trim() === '' ? [] : [{ label: securityPoolCopy.vault, value: }]), - ] -} - -function getSecurityVaultActionTitle(actionName: SecurityVaultActionResult['action']) { - if (actionName === 'depositRepToVault') return securityPoolCopy.depositRepToVault - if (actionName === 'queueWithdrawRep') return securityPoolCopy.withdrawRep - return humanizeAction(actionName) -} - -export function createSecurityVaultTransactionIntent(actionName: SecurityVaultActionResult['action'], context?: SecurityVaultTransactionContext) { - return buildIntent({ - action: actionName, - rows: getSecurityVaultTransactionRows(context), - source: 'security-vault', - submittedTitle: getSecurityVaultActionTitle(actionName), - universeId: context?.universeId, - }) -} - -export function createSecurityVaultSuccessPresentation(result: SecurityVaultActionResult, context?: SecurityVaultTransactionContext) { - let queuedOperationDetail: string | undefined - if (result.queuedOperation !== undefined) { - queuedOperationDetail = result.queuedOperation.isPendingSlot ? transactionCopy.formatQueuedOperationAutoExecutionDetail(result.queuedOperation.operationId.toString()) : transactionCopy.formatQueuedOperationManualExecutionDetail(result.queuedOperation.operationId.toString()) - } - return buildPresentation({ - ...(queuedOperationDetail === undefined ? {} : { detail: queuedOperationDetail }), - hash: result.hash, - rows: [...(getSecurityVaultTransactionRows(context) ?? []), ...(result.queuedOperation === undefined ? [] : [{ label: commonCopy.stagedOperation, value: `#${result.queuedOperation.operationId.toString()}` }])], - title: getSecurityVaultActionTitle(result.action), - tone: 'success', - universeId: context?.universeId, - }) -} - -export function createSecurityVaultWarningPresentation(result: SecurityVaultActionResult, message: string, context?: SecurityVaultTransactionContext) { - return withWarning(createSecurityVaultSuccessPresentation(result, context), message) -} - -type PoolUniverseTransactionContext = { - securityPoolAddress?: string | undefined - universeId?: bigint | undefined -} - -function getPoolUniverseTransactionRows(context: PoolUniverseTransactionContext | undefined) { - if (context === undefined) return undefined - return [...(context.securityPoolAddress === undefined || context.securityPoolAddress.trim() === '' ? [] : [{ identityKey: 'security-pool', label: transactionCopy.pool, value: }])] -} - -type TradingTransactionContext = PoolUniverseTransactionContext & { - shareOutcome?: ReportingActionResult['outcome'] | undefined -} - -function getTradingTransactionRows(context: TradingTransactionContext | undefined) { - return [...(getPoolUniverseTransactionRows(context) ?? []), ...(context?.shareOutcome === undefined ? [] : [{ identityKey: 'outcome', label: transactionCopy.shareOutcome, value: getReportingOutcomeLabel(context.shareOutcome) }])] -} - -export function createTradingTransactionIntent(actionName: TradingActionResult['action'], context?: TradingTransactionContext) { - return buildIntent({ - action: actionName, - rows: getTradingTransactionRows(context), - source: 'trading', - submittedTitle: humanizeAction(actionName), - universeId: context?.universeId, - }) -} - -export function createTradingSuccessPresentation(result: TradingActionResult) { - const detail = (() => { - if (result.action === 'createCompleteSet') return undefined - if (result.action === 'redeemCompleteSet') return transactionCopy.completeSetBurnSuccessDetail - if (result.action === 'migrateShares') return transactionCopy.parentPoolSharesMigratedDetail - return undefined - })() - return buildPresentation({ - ...(detail === undefined ? {} : { detail }), - hash: result.hash, - rows: [ - { identityKey: 'security-pool', label: transactionCopy.pool, value: }, - ...(result.shareOutcome === undefined ? [] : [{ identityKey: 'outcome', label: transactionCopy.shareOutcome, value: getReportingOutcomeLabel(result.shareOutcome) }]), - ...(result.targetOutcomeIndexes === undefined ? [] : [{ label: transactionCopy.targetOutcomeIndexes, value: result.targetOutcomeIndexes.join(', ') }]), - ], - title: humanizeAction(result.action), - tone: 'success', - universeId: result.universeId, - }) -} - -export function createTradingWarningPresentation(result: TradingActionResult, message: string) { - return withWarning(createTradingSuccessPresentation(result), message) -} - -type ReportingTransactionContext = PoolUniverseTransactionContext & { - outcome?: ReportingActionResult['outcome'] | undefined -} - -function getReportingTransactionRows(context: ReportingTransactionContext | undefined) { - return [...(getPoolUniverseTransactionRows(context) ?? []), ...(context?.outcome === undefined ? [] : [{ label: commonCopy.outcome, value: getReportingOutcomeLabel(context.outcome) }])] -} - -export function createReportingTransactionIntent(actionName: ReportingActionResult['action'], context?: ReportingTransactionContext) { - return buildIntent({ - action: actionName, - rows: getReportingTransactionRows(context), - source: 'reporting', - submittedTitle: humanizeAction(actionName), - universeId: context?.universeId, - }) -} - -export function createReportingSuccessPresentation(result: ReportingActionResult) { - const detail = result.action === 'reportOutcome' ? transactionCopy.reportingContributionSuccessDetail : transactionCopy.escalationDepositsSettledDetail - return buildPresentation({ - detail, - hash: result.hash, - rows: [ - { label: transactionCopy.pool, value: }, - { label: commonCopy.outcome, value: getReportingOutcomeLabel(result.outcome) }, - ], - title: humanizeAction(result.action), - tone: 'success', - universeId: result.universeId, - }) -} - -export function createReportingWarningPresentation(result: ReportingActionResult, message: string) { - return withWarning(createReportingSuccessPresentation(result), message) -} - -type LiquidationTransactionContext = PoolUniverseTransactionContext & { - amount?: string | undefined - targetVault?: string | undefined -} - -function getLiquidationTransactionRows(context: LiquidationTransactionContext | undefined) { - return [ - ...(getPoolUniverseTransactionRows(context) ?? []), - ...(context?.targetVault === undefined || context.targetVault.trim() === '' ? [] : [{ label: commonCopy.targetVault, value: }]), - ...(context?.amount === undefined || context.amount.trim() === '' ? [] : [{ label: securityPoolCopy.requestedLiquidationDebt, value: formatValueWithUnit(context.amount.trim(), commonCopy.eth) }]), - ] -} - -export function createLiquidationTransactionIntent(context?: LiquidationTransactionContext) { - return buildIntent({ - action: 'queueLiquidation', - rows: getLiquidationTransactionRows(context), - source: 'security-pools', - submittedTitle: transactionCopy.submittingLiquidation, - universeId: context?.universeId, - }) -} - -export function createLiquidationSuccessPresentation(result: SecurityPoolOverviewActionResult, context?: LiquidationTransactionContext) { - let queuedOperationDetail: string = transactionCopy.liquidationRequestSubmittedDetail - if (result.queuedOperation !== undefined) { - queuedOperationDetail = result.queuedOperation.isPendingSlot ? transactionCopy.formatQueuedLiquidationAutoExecutionDetail(result.queuedOperation.operationId.toString()) : transactionCopy.formatQueuedLiquidationManualExecutionDetail(result.queuedOperation.operationId.toString()) - } - return buildPresentation({ - detail: result.stagedExecution?.success === true ? transactionCopy.liquidationExecutedImmediatelyDetail : queuedOperationDetail, - hash: result.hash, - rows: [...getLiquidationTransactionRows({ ...context, securityPoolAddress: result.securityPoolAddress }), ...(result.queuedOperation === undefined ? [] : [{ label: commonCopy.stagedOperation, value: `#${result.queuedOperation.operationId.toString()}` }])], - title: result.stagedExecution?.success === true ? commonCopy.liquidationExecuted : commonCopy.liquidationSubmitted, - tone: 'success', - universeId: context?.universeId, - }) -} - -export function createLiquidationFailurePresentation(result: SecurityPoolOverviewActionResult, detail: string, context?: LiquidationTransactionContext) { - return buildPresentation({ - detail, - hash: result.hash, - rows: [...getLiquidationTransactionRows({ ...context, securityPoolAddress: result.securityPoolAddress }), ...(result.stagedExecution === undefined ? [] : [{ label: commonCopy.stagedOperation, value: `#${result.stagedExecution.operationId.toString()}` }])], - title: commonCopy.liquidationFailed, - tone: 'error', - universeId: context?.universeId, - }) -} - -export function createLiquidationWarningPresentation(result: SecurityPoolOverviewActionResult, message: string, context?: LiquidationTransactionContext) { - return withWarning(createLiquidationSuccessPresentation(result, context), message) -} - -type PoolOracleTransactionContext = { - managerAddress: string - securityPoolAddress?: string | undefined - universeId?: bigint | undefined -} - -function getPoolOracleTransactionRows(context: PoolOracleTransactionContext | undefined) { - if (context === undefined) return undefined - return [...(context.securityPoolAddress === undefined ? [] : [{ label: commonCopy.securityPoolAddress, value: }]), { label: securityPoolCopy.oracleManager, value: }] -} - -export function createPoolOracleTransactionIntent(actionName: 'executeStagedOperation' | 'requestPrice', context?: PoolOracleTransactionContext) { - let submittedTitle: string = transactionCopy.executingStagedOperation - if (actionName === 'requestPrice') { - submittedTitle = transactionCopy.requestingPrice - } - return buildIntent({ - action: actionName, - rows: getPoolOracleTransactionRows(context), - source: 'pool-oracle', - submittedTitle, - universeId: context?.universeId, - }) -} - -export function createPoolOracleSuccessPresentation(result: OpenOracleActionResult, context?: PoolOracleTransactionContext) { - let title: string = transactionCopy.stagedOperationExecuted - if (result.action === 'requestPrice') { - title = transactionCopy.priceRequested - } - return buildPresentation({ - hash: result.hash, - rows: getPoolOracleTransactionRows(context), - title, - tone: 'success', - universeId: context?.universeId, - }) -} - -export function createPoolOracleWarningPresentation(result: OpenOracleActionResult, message: string, context?: PoolOracleTransactionContext) { - return withWarning(createPoolOracleSuccessPresentation(result, context), message) -} - -type OpenOracleTransactionContext = { - openOracleAddress?: string | undefined - reportId?: string | undefined - token1Symbol?: string | undefined - token2Symbol?: string | undefined - tokenPair?: string | undefined - withdrawalTokenSymbol?: string | undefined -} - -function getOpenOracleTransactionRows(context: OpenOracleTransactionContext | undefined) { - if (context === undefined) return undefined - return [ - ...(context.reportId === undefined || context.reportId.trim() === '' ? [] : [{ label: openOracleCopy.reportId, value: context.reportId }]), - ...(context.tokenPair === undefined || context.tokenPair.trim() === '' ? [] : [{ label: openOracleCopy.tokenPair, value: context.tokenPair }]), - ...(context.openOracleAddress === undefined ? [] : [{ label: openOracleCopy.oracleAddress, value: }]), - ] -} - -function getOpenOracleSubmittedTitle(actionName: OpenOracleActionResult['action'], context: OpenOracleTransactionContext | undefined) { - if (actionName === 'approveToken1') return openOracleCopy.formatApproveToken(context?.token1Symbol ?? openOracleCopy.baseToken) - if (actionName === 'approveToken2') return openOracleCopy.formatApproveToken(context?.token2Symbol ?? openOracleCopy.quoteToken) - if (actionName === 'createReportInstance') return openOracleCopy.createReport - if (actionName === 'settle') return openOracleCopy.settlingReportTitle - if (actionName === 'withdrawBalance') return openOracleCopy.withdrawBalance(context?.withdrawalTokenSymbol ?? openOracleCopy.oracleBalance) - return humanizeAction(actionName) -} - -function getOpenOracleSuccessTitle(actionName: OpenOracleActionResult['action'], context: OpenOracleTransactionContext | undefined) { - if (actionName === 'approveToken1') return openOracleCopy.formatTokenApproved(context?.token1Symbol ?? openOracleCopy.baseToken) - if (actionName === 'approveToken2') return openOracleCopy.formatTokenApproved(context?.token2Symbol ?? openOracleCopy.quoteToken) - if (actionName === 'createReportInstance') return openOracleCopy.reportCreated - if (actionName === 'settle') return openOracleCopy.reportSettled - if (actionName === 'withdrawBalance') return openOracleCopy.formatTokenWithdrawn(context?.withdrawalTokenSymbol ?? openOracleCopy.oracleBalance) - return humanizeAction(actionName) -} - -export function createOpenOracleTransactionIntent(actionName: OpenOracleActionResult['action'], context?: OpenOracleTransactionContext) { - return buildIntent({ - action: actionName, - rows: getOpenOracleTransactionRows(context), - source: 'open-oracle', - submittedTitle: getOpenOracleSubmittedTitle(actionName, context), - }) -} - -export function createOpenOracleSuccessPresentation(result: OpenOracleActionResult, context?: OpenOracleTransactionContext) { - return buildPresentation({ - hash: result.hash, - rows: getOpenOracleTransactionRows(context), - title: getOpenOracleSuccessTitle(result.action, context), - tone: 'success', - }) -} - -export function createOpenOracleWarningPresentation(result: OpenOracleActionResult, message: string, context?: OpenOracleTransactionContext) { - return withWarning(createOpenOracleSuccessPresentation(result, context), message) -} - -export function createForkAuctionTransactionIntent(actionName: ForkAuctionActionResult['action'], { context, submittedTitle }: { context?: PoolUniverseTransactionContext; submittedTitle?: TransactionIntent['submittedTitle'] } = {}) { - let resolvedSubmittedTitle = submittedTitle - if (resolvedSubmittedTitle === undefined) { - if (actionName === 'migrateUnresolvedEscalation') { - resolvedSubmittedTitle = transactionCopy.clearUnresolvedParentEscalationDepositAccounting - } else if (actionName === 'claimParentEscalationDeposits') { - resolvedSubmittedTitle = transactionCopy.claimParentEscalationDeposits - } else { - resolvedSubmittedTitle = humanizeAction(actionName) - } - } - return buildIntent({ - action: actionName, - rows: getPoolUniverseTransactionRows(context), - source: 'fork-auction', - submittedTitle: resolvedSubmittedTitle, - universeId: context?.universeId, - }) -} - -export function createForkAuctionSuccessPresentation(result: ForkAuctionActionResult) { - let title = humanizeAction(result.action) - if (result.action === 'claimAuctionProceeds' && result.settlementMode === 'refund') { - title = transactionCopy.settleFinalizedRefunds - } else if (result.action === 'migrateUnresolvedEscalation') { - title = transactionCopy.clearUnresolvedParentEscalationDepositAccounting - } else if (result.action === 'claimParentEscalationDeposits') { - title = transactionCopy.claimParentEscalationDeposits - } - const detail = (() => { - switch (result.action) { - case 'claimAuctionProceeds': - if (result.settlementMode === 'refund') { - return transactionCopy.formatFinalizedRefundSettlementResultDetail(AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL) - } - if (result.settlementMode === 'claim') { - return transactionCopy.formatWinningBidSettlementResultDetail(AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL) - } - return transactionCopy.formatMixedBidSettlementResultDetail(AUCTIONED_CAPACITY_OWNERSHIP_ATTO_REP_LABEL) - case 'createChildUniverse': - return transactionCopy.childUniverseLinkedToForkPathDetail - case 'forkWithOwnEscalation': - return transactionCopy.ownEscalationForkSubmittedDetail - case 'forkUniverse': - return transactionCopy.zoltarUniverseForkSubmittedDetail - case 'initiateFork': - return transactionCopy.poolReadyForForkMigrationDetail - case 'claimParentEscalationDeposits': - return transactionCopy.parentEscalationDepositsClaimedDetail - case 'migrateRepToZoltar': - return transactionCopy.poolRepMigrationSuccessDetail - case 'migrateUnresolvedEscalation': - return transactionCopy.unresolvedEscalationMigratedDetail - case 'migrateVault': - return transactionCopy.vaultMigratedDetail - case 'refundLosingBids': - return transactionCopy.losingBidsRefundedDetail - case 'settleForkedEscalation': - return transactionCopy.forkDepositSettlementSuccessDetail - case 'startTruthAuction': - return transactionCopy.truthAuctionStartedSuccessDetail - case 'submitBid': - return transactionCopy.truthAuctionBidSuccessDetail - default: - return undefined - } - })() - return buildPresentation({ - ...(detail === undefined ? {} : { detail }), - hash: result.hash, - rows: [{ label: transactionCopy.pool, value: }], - title, - tone: 'success', - universeId: result.universeId, - }) -} - -export function createForkAuctionWarningPresentation(result: ForkAuctionActionResult, message: string) { - return withWarning(createForkAuctionSuccessPresentation(result), message) -} diff --git a/ui/ts/index.dev.ts b/ui/ts/index.dev.ts deleted file mode 100644 index eb4544bc8..000000000 --- a/ui/ts/index.dev.ts +++ /dev/null @@ -1,4 +0,0 @@ -import './liveReload.js' -import { mountApp } from './app/appRoot.js' - -void mountApp() diff --git a/ui/ts/index.ts b/ui/ts/index.ts deleted file mode 100644 index 363ba224a..000000000 --- a/ui/ts/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mountApp } from './app/appRoot.js' - -void mountApp() diff --git a/ui/ts/lib/routing.ts b/ui/ts/lib/routing.ts deleted file mode 100644 index 72552adf5..000000000 --- a/ui/ts/lib/routing.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { Route } from '../types/app.js' - -export const DEPLOY_ROUTE = '#/deploy' -export const ZOLTAR_ROUTE = '#/zoltar' -export const SECURITY_POOLS_ROUTE = '#/security-pools' -export const OPEN_ORACLE_ROUTE = '#/open-oracle' -const DEFAULT_ROUTE: Exclude = 'zoltar' -const ROUTE_NAMES: Exclude[] = ['deploy', 'zoltar', 'security-pools', 'open-oracle'] -const ROUTE_HASHES: Record, string> = { - deploy: DEPLOY_ROUTE, - zoltar: ZOLTAR_ROUTE, - 'security-pools': SECURITY_POOLS_ROUTE, - 'open-oracle': OPEN_ORACLE_ROUTE, -} -const SHARED_ROUTE_QUERY_PARAMETERS = new Set(['network', 'rpcUrl', 'simScenario', 'simState', 'simulate', 'universe']) -const ROUTE_QUERY_PARAMETERS: Record, ReadonlySet> = { - deploy: new Set(), - zoltar: new Set(['zoltarView']), - 'security-pools': new Set(['questionId', 'securityPool', 'securityPoolsView', 'selectedPoolView']), - 'open-oracle': new Set(['openOracleReportId', 'openOracleView']), -} - -const ROUTE_BY_HASH = ROUTE_NAMES.reduce>>((routeByHash, route) => { - routeByHash[ROUTE_HASHES[route]] = route - return routeByHash -}, {}) - -function splitRouteHash(hash: string) { - const queryIndex = hash.indexOf('?') - if (queryIndex === -1) - return { - routeHash: hash, - search: '', - } - - return { - routeHash: hash.slice(0, queryIndex), - search: hash.slice(queryIndex), - } -} - -export function ensureRouteHash() { - if (window.location.hash === '') window.location.hash = ROUTE_HASHES[DEFAULT_ROUTE] -} - -export function getCurrentRoute() { - const { routeHash } = splitRouteHash(window.location.hash) - return ROUTE_BY_HASH[routeHash] ?? (routeHash === '' ? DEFAULT_ROUTE : 'not-found') -} - -export function getRouteHash(route: Exclude) { - return ROUTE_HASHES[route] -} - -export function getRouteHashSearch(hash = window.location.hash) { - return splitRouteHash(hash).search -} - -export function getTopLevelRouteSearch(nextRoute: Exclude, search = getRouteHashSearch(), preservedParameters: ReadonlySet = new Set()) { - const destinationParameters = ROUTE_QUERY_PARAMETERS[nextRoute] - const sourceParameters = new URLSearchParams(search) - const destinationSearch = new URLSearchParams() - for (const [key, value] of sourceParameters) { - if (SHARED_ROUTE_QUERY_PARAMETERS.has(key) || destinationParameters.has(key) || preservedParameters.has(key)) destinationSearch.append(key, value) - } - const serializedSearch = destinationSearch.toString() - return serializedSearch === '' ? '' : `?${serializedSearch}` -} - -export function getCurrentRouteHash() { - const { routeHash } = splitRouteHash(window.location.hash) - return routeHash === '' ? ROUTE_HASHES[DEFAULT_ROUTE] : routeHash -} - -export function buildRouteHref(routeHash: string, search: string) { - return `${routeHash}${search}` -} diff --git a/ui/ts/simulation/scenarios.ts b/ui/ts/simulation/scenarios.ts deleted file mode 100644 index 4ad01b589..000000000 --- a/ui/ts/simulation/scenarios.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { assertNever } from '../lib/assert.js' - -export const SIMULATION_SCENARIOS = ['baseline', 'deployed', 'security-pool', 'securitypoolx2', 'securitypoolx2-auction'] as const - -export type SimulationScenario = (typeof SIMULATION_SCENARIOS)[number] - -export function isSimulationScenario(value: string): value is SimulationScenario { - return SIMULATION_SCENARIOS.includes(value as SimulationScenario) -} - -export function normalizeSimulationScenario(value: string | undefined): SimulationScenario { - return value !== undefined && isSimulationScenario(value) ? value : 'baseline' -} - -export function getSimulationScenarioLabel(scenario: SimulationScenario) { - switch (scenario) { - case 'baseline': - return 'Baseline' - case 'deployed': - return 'Deployed' - case 'security-pool': - return 'Security pool' - case 'securitypoolx2': - return 'Security pool x2' - case 'securitypoolx2-auction': - return 'Security pool x2 auction' - default: - return assertNever(scenario) - } -} - -export function getSimulationScenarioDescription(scenario: SimulationScenario) { - switch (scenario) { - case 'baseline': - return 'Fresh walletless simulation with funded QA accounts and no app contracts deployed. Use it to test the Deploy flow from scratch.' - case 'deployed': - return 'App contracts are deployed, but no security pools or questions are created. Use it to test setup flows from an empty deployment.' - case 'security-pool': - return 'One seeded question, one security pool, and one funded vault with an active capacity ownership. Use it to test pool actions and liquidation paths.' - case 'securitypoolx2': - return 'Two seeded questions with two security pools and two funded vaults in each pool. Use it to test multi-pool selection and repeated pool actions.' - case 'securitypoolx2-auction': - return 'Two seeded questions with one own-escalation fork already triggered and one child truth auction seeded with ten bids. Use it to test the fork-auction bidbook and settlement actions.' - default: - return assertNever(scenario) - } -} diff --git a/ui/ts/tests/features/markets/marketForm.test.ts b/ui/ts/tests/features/markets/marketForm.test.ts deleted file mode 100644 index 455c85a97..000000000 --- a/ui/ts/tests/features/markets/marketForm.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/// - -import { describe, expect, test } from 'bun:test' -import { - getDefaultForkAuctionFormState, - getDefaultMarketFormState, - getDefaultOpenOracleCreateFormState, - getDefaultOpenOracleFormState, - getDefaultReportingFormState, - getDefaultSecurityPoolFormState, - getDefaultSecurityVaultFormState, - getDefaultTradingFormState, - getDefaultZoltarMigrationFormState, - parseBigIntInput, - parseOptionalRepAmountInput, - parseRepAmountInput, - parseTimestampInput, - parseTradingAmountInput, - tryParseTimestampInput, -} from '../../../features/markets/lib/marketForm.js' - -describe('market form defaults and conversion helpers', () => { - test('returns stable default form snapshots across all supported forms', () => { - expect(getDefaultMarketFormState().marketType).toBe('binary') - expect(getDefaultMarketFormState().categoricalOutcomes).toEqual(['Yes', 'No']) - expect(getDefaultSecurityPoolFormState().statoblastSecurityMultiplierBps).toBe('2') - expect(getDefaultSecurityPoolFormState().initialReportPriorityFeeGwei).toBe('10') - expect(getDefaultSecurityVaultFormState().depositAmount).toBe('0') - expect(getDefaultSecurityVaultFormState().stagedOperationTimeoutMinutes).toBe('5') - expect(getDefaultReportingFormState().selectedOutcome).toBeUndefined() - expect(getDefaultReportingFormState().selectedWithdrawDepositIndexesByOutcome).toEqual({ invalid: [], yes: [], no: [] }) - expect(getDefaultTradingFormState().selectedShareOutcome).toBe('yes') - expect(getDefaultOpenOracleFormState().stateHash).toBe('0x0000000000000000000000000000000000000000000000000000000000000000') - expect(getDefaultOpenOracleCreateFormState().multiplier).toBe('100') - expect(getDefaultOpenOracleCreateFormState().disputeDelay).toBe('3600') - expect(getDefaultOpenOracleCreateFormState().settlementTime).toBe('86400') - expect(getDefaultForkAuctionFormState().repMigrationOutcomes).toBe('yes') - expect(getDefaultZoltarMigrationFormState().amount).toBe('0.0') - expect(getDefaultZoltarMigrationFormState().outcomeIndexes).toBe('') - }) - - test('parses integers and rejects malformed decimal values', () => { - expect(parseBigIntInput('10', 'Retention rate')).toBe(10n) - expect(() => parseBigIntInput('abc', 'Retention rate')).toThrow('Retention rate must be a whole number') - expect(parseBigIntInput(' 11 ', 'Retention rate')).toBe(11n) - expect(() => parseBigIntInput(' ', 'Retention rate')).toThrow('Retention rate is required') - }) - - test('parses REP amounts and propagates parsing errors', () => { - expect(parseRepAmountInput('2.5', 'REP amount')).toBe(2500000000000000000n) - expect(() => parseRepAmountInput('x', 'REP amount')).toThrow('REP amount must be a decimal number') - expect(parseRepAmountInput(' 0.5 ', 'REP amount')).toBe(500000000000000000n) - expect(parseOptionalRepAmountInput('')).toBeUndefined() - expect(parseOptionalRepAmountInput('abc')).toBeUndefined() - expect(parseOptionalRepAmountInput('1.25')).toBe(1_250_000_000_000_000_000n) - }) - - test('parses trading inputs as 18 decimal fixed-point values', () => { - expect(parseTradingAmountInput('0.000000000000000001', 'Trade amount')).toBe(1n) - expect(() => parseTradingAmountInput('abc', 'Trade amount')).toThrow('Trade amount must be a decimal number') - }) - - test('throws clear message for malformed timestamps and parses valid date strings', () => { - expect(parseTimestampInput('2025-01-01T00:00:00Z', 'End time')).toBeGreaterThan(0n) - expect(() => parseTimestampInput('nonsense', 'End time')).toThrow('End time is invalid') - expect(tryParseTimestampInput('2025-01-01T00:00:00Z')).toBeGreaterThan(0n) - expect(tryParseTimestampInput('nonsense')).toBeUndefined() - }) -}) diff --git a/ui/ts/tests/features/markets/marketSection.test.tsx b/ui/ts/tests/features/markets/marketSection.test.tsx deleted file mode 100644 index 763652ec8..000000000 --- a/ui/ts/tests/features/markets/marketSection.test.tsx +++ /dev/null @@ -1,1285 +0,0 @@ -/// - -import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' -import { h } from 'preact' -import { render } from 'preact' -import { act } from 'preact/test-utils' -import { useState } from 'preact/hooks' -import { zeroAddress } from '@zoltar/shared/ethereum' -import { MarketSection } from '../../../features/markets/components/MarketSection.js' -import { getDefaultMarketFormState, getDefaultZoltarMigrationFormState } from '../../../features/markets/lib/marketForm.js' -import type { AccountState } from '../../../types/app.js' -import type { MarketSectionProps } from '../../../features/types.js' -import type { ListedSecurityPool, ZoltarUniverseSummary } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled } from '../../testUtils/transactionActionButton.js' -import { ChainTimestampContext } from '../../../lib/chainTimestamp.js' - -const ATTO_REP = 10n ** 18n - -function createDeferred() { - let resolve: (value: T) => void = () => undefined - let reject: (reason?: unknown) => void = () => undefined - const promise = new Promise((promiseResolve, promiseReject) => { - resolve = promiseResolve - reject = promiseReject - }) - return { promise, reject, resolve } -} - -function createAccountState(overrides: Partial = {}): AccountState { - return { - address: zeroAddress, - chainId: '0x1', - ethBalanceAttoEth: 0n, - wethBalanceAttoEth: 0n, - ...overrides, - } -} - -function createZoltarUniverse(overrides: Partial = {}): ZoltarUniverseSummary { - return { - childUniverses: [], - forkThresholdAttoRep: 100n, - forkQuestionDetails: undefined, - forkTime: 0n, - forkingOutcomeIndex: 0n, - hasForked: false, - parentUniverseId: 0n, - reputationToken: zeroAddress, - totalTheoreticalSupplyAttoRep: 1000n, - universeId: 1n, - ...overrides, - } -} - -function createBinaryForkQuestion() { - return { - answerUnit: '', - createdAt: 1n, - description: 'Fork question', - displayValueMax: 2n, - displayValueMin: 0n, - endTime: 2n, - exists: true, - marketType: 'binary' as const, - numTicks: 2n, - outcomeLabels: ['Yes', 'No'], - questionId: '0x01', - startTime: 1n, - title: 'Fork question title', - } -} - -function createLinkedSecurityPool(questionId: string): ListedSecurityPool { - return { - settlementCollateralAttoEth: 4n, - currentRetentionRate: 0n, - feeEligibleCapacityOwnershipAttoRep: 4n, - forkOutcome: 'none', - forkOwnSecurityPool: false, - hasForkActivity: false, - hasLoadedVaults: true, - initialReportPriorityFeeAttoEthPerGas: 10_000_000_000n, - lastOraclePrice: undefined, - lastOracleSettlementTimestamp: 0n, - managerAddress: zeroAddress, - marketDetails: createBinaryForkQuestion(), - migratedAttoRep: 0n, - parent: zeroAddress, - questionId, - questionOutcome: 'none', - statoblastSecurityMultiplierBps: 20_000n, - securityPoolAddress: '0x0000000000000000000000000000000000000011', - shareTokenSupplyAttoShares: 4n, - systemState: 'operational', - totalPoolHeldAttoRep: 10n, - totalCapacityOwnershipAttoRep: 4n, - truthAuctionAddress: zeroAddress, - truthAuctionStartedAt: 0n, - universeHasForked: false, - universeId: 11n, - vaultCount: 0n, - vaults: [], - } -} - -function createMarketSectionProps(overrides: Partial = {}): MarketSectionProps { - return { - accountState: createAccountState(), - activeUniverseId: 1n, - activeView: 'questions', - environmentRefreshKey: 0, - hasLoadedSecurityPools: true, - hasLoadedZoltarQuestions: false, - loadingZoltarForkAccess: false, - zoltarForkActiveAction: undefined, - loadingZoltarQuestionCount: false, - loadingZoltarQuestion: false, - loadingZoltarQuestions: false, - loadingZoltarUniverse: false, - loadingSecurityPools: false, - marketForm: getDefaultMarketFormState(), - marketCreating: false, - marketError: undefined, - marketResult: undefined, - onActiveViewChange: () => undefined, - onApproveZoltarForkRep: () => undefined, - onCreateChildUniverseForOutcomeIndex: () => undefined, - onCreateMarket: () => undefined, - onForkZoltar: () => undefined, - onLoadZoltarQuestionPage: async () => undefined, - onLoadZoltarQuestion: async () => undefined, - onLoadZoltarQuestions: async () => undefined, - onLoadSecurityPools: () => undefined, - onMarketFormChange: () => undefined, - onMigrateInternalRep: () => undefined, - onPrepareRepForMigration: () => undefined, - onResetMarket: () => undefined, - onUseQuestionForFork: () => undefined, - onUseQuestionForPool: () => undefined, - onZoltarForkQuestionIdChange: () => undefined, - onZoltarMigrationFormChange: () => undefined, - zoltarChildUniverseError: undefined, - zoltarChildUniversePendingOutcomeIndex: undefined, - zoltarForkApproval: { - error: undefined, - loading: false, - value: 0n, - }, - zoltarForkError: undefined, - zoltarForkPending: false, - zoltarForkQuestionId: '', - zoltarForkRepBalanceAttoRep: 0n, - zoltarMigrationChildRepBalancesAttoRep: {}, - zoltarMigrationActiveAction: undefined, - zoltarMigrationError: undefined, - zoltarMigrationForm: getDefaultZoltarMigrationFormState(), - zoltarMigrationPending: false, - zoltarMigrationPreparedRepBalanceAttoRep: 0n, - zoltarQuestionCount: 0n, - zoltarQuestionLookupError: undefined, - zoltarQuestionLookupId: undefined, - zoltarQuestionPage: undefined, - zoltarQuestions: [], - zoltarQuestionsError: undefined, - zoltarUniverse: createZoltarUniverse(), - zoltarUniverseState: 'ready', - securityPoolsLoadError: undefined, - ...overrides, - } -} - -describe('MarketSection', () => { - let restoreDomEnvironment: (() => void) | undefined - let cleanupRenderedComponent: (() => Promise) | undefined - - beforeEach(() => { - const domEnvironment = installDomEnvironment() - restoreDomEnvironment = domEnvironment.cleanup - window.history.replaceState({}, '', 'http://localhost/') - }) - - afterEach(async () => { - await cleanupRenderedComponent?.() - cleanupRenderedComponent = undefined - restoreDomEnvironment?.() - restoreDomEnvironment = undefined - }) - - test('renders the Zoltar title without local view tabs in the section header', async () => { - const renderedComponent = await renderIntoDocument(h(MarketSection, createMarketSectionProps())) - cleanupRenderedComponent = renderedComponent.cleanup - - const questionsTitle = document.body.querySelector('h3') - if (!(questionsTitle instanceof HTMLElement)) throw new Error('Expected to find the Zoltar section heading') - expect(questionsTitle.textContent).toBe('Zoltar') - - const sectionHeader = questionsTitle.closest('.section-block-header') - if (sectionHeader === null) throw new Error('Expected Zoltar title to render inside a section header') - expect(sectionHeader.querySelector('[role="tablist"][aria-label="Zoltar views"]')).toBeNull() - }) - - test('shows question load failures in the questions section with a retry', async () => { - let retryCalls = 0 - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - onLoadZoltarQuestions: async () => { - retryCalls += 1 - }, - zoltarQuestionCount: undefined, - zoltarQuestionsError: 'Question service unavailable', - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - expect(documentQueries.getByRole('alert').textContent).toContain('Question service unavailable') - fireEvent.click(documentQueries.getByRole('button', { name: 'Retry questions' })) - await waitFor(() => expect(retryCalls).toBe(1)) - }) - - test('retries a failed question page only once', async () => { - let pageLoadCalls = 0 - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - onLoadZoltarQuestionPage: async () => { - pageLoadCalls += 1 - throw new Error('Question page unavailable') - }, - zoltarQuestionCount: 1n, - zoltarQuestionsError: 'Question page unavailable', - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - await waitFor(() => expect(pageLoadCalls).toBe(1)) - expect(documentQueries.queryByText('Question page unavailable.')).toBeNull() - fireEvent.click(documentQueries.getByRole('button', { name: 'Retry questions' })) - await waitFor(() => expect(pageLoadCalls).toBe(2)) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(pageLoadCalls).toBe(2) - }) - - test('does not repeat the active universe outside the header', async () => { - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeUniverseId: 10n, - activeView: 'create', - zoltarUniverse: createZoltarUniverse({ universeId: 10n }), - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - expect(within(document.body).queryByText('Universe 0xa')).toBeNull() - const contextSummary = document.body.querySelector('.market-task-context') - if (!(contextSummary instanceof HTMLElement)) throw new Error('Expected task-focused market context') - expect(contextSummary.querySelector('.data-grid')).not.toBeNull() - }) - - test('links a question directly to its child-universe shares and reporting workflows', async () => { - const question = createBinaryForkQuestion() - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - securityPools: [createLinkedSecurityPool(question.questionId)], - zoltarQuestionCount: 1n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 1n, - questions: [question], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - const sharesLink = documentQueries.getByRole('link', { name: 'Open shares & position for Fork question title in universe 11 (0x0000000000000000000000000000000000000011)' }) - const reportingLink = documentQueries.getByRole('link', { name: 'Open reporting for Fork question title in universe 11 (0x0000000000000000000000000000000000000011)' }) - expect(sharesLink.getAttribute('href')).toContain('universe=11') - expect(sharesLink.getAttribute('href')).toContain('selectedPoolView=trading') - expect(reportingLink.getAttribute('href')).toContain('selectedPoolView=reporting') - expect(documentQueries.getByText('Complete sets only')).not.toBeNull() - expect(document.body.textContent?.includes('Mint or redeem matching Yes, No, and Invalid shares.')).toBe(false) - expect(documentQueries.getByText('Open Interest')).not.toBeNull() - expect(documentQueries.getByText('Share Supply')).not.toBeNull() - const universeWarning = documentQueries.getByRole('alert') - expect(universeWarning.textContent).toContain('Universe mismatch') - expect(universeWarning.textContent).toContain('while the header shows 0x1') - expect(universeWarning.classList.contains('market-linked-pool-universe-warning')).toBe(true) - expect(universeWarning.classList.contains('compact')).toBe(true) - const shareSupply = document.body.querySelector('.market-linked-pool-share-supply') - if (!(shareSupply instanceof HTMLElement)) throw new Error('Expected a compact linked-pool share supply value') - expect(shareSupply.title).toBe('0.000000000000000004') - }) - - test('distinguishes linked-pool loading, failure recovery, and a confirmed empty result', async () => { - const question = createBinaryForkQuestion() - const onLoadSecurityPools = mock(() => undefined) - const baseProps = createMarketSectionProps({ - hasLoadedSecurityPools: false, - loadingSecurityPools: true, - onLoadSecurityPools, - zoltarQuestionCount: 1n, - zoltarQuestionPage: { pageIndex: 0, pageSize: 10, questionCount: 1n, questions: [question] }, - }) - const renderedComponent = await renderIntoDocument(h(MarketSection, baseProps)) - cleanupRenderedComponent = renderedComponent.cleanup - - expect(within(document.body).getByText('Loading linked pools…')).not.toBeNull() - expect(within(document.body).queryByText('No tradeable pool is linked to this question yet.')).toBeNull() - - await act(() => { - render(h(MarketSection, { ...baseProps, loadingSecurityPools: false, securityPoolsLoadError: 'Pool lookup failed' }), renderedComponent.container) - }) - expect(within(document.body).getByText('Pool lookup failed')).not.toBeNull() - await act(() => { - fireEvent.click(within(document.body).getByRole('button', { name: 'Retry linked pools' })) - }) - expect(onLoadSecurityPools).toHaveBeenCalledTimes(1) - - await act(() => { - render(h(MarketSection, { ...baseProps, hasLoadedSecurityPools: true, loadingSecurityPools: false, securityPoolsLoadError: undefined }), renderedComponent.container) - }) - expect(within(document.body).getByText('No tradeable pool is linked to this question yet.')).not.toBeNull() - }) - - test('auto-loads questions once when opening the questions view without loaded data', async () => { - const calls: string[] = [] - const initialProps = createMarketSectionProps({ - activeUniverseId: 7n, - loadingZoltarQuestions: false, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`${pageIndex}:${pageSize}`) - }, - zoltarQuestionCount: 3n, - zoltarUniverse: createZoltarUniverse({ universeId: 7n }), - }) - - const renderedComponent = await renderIntoDocument(h(MarketSection, initialProps)) - cleanupRenderedComponent = renderedComponent.cleanup - expect(within(document.body).getByText('Loading questions…')).not.toBeNull() - await waitFor(() => { - expect(calls).toEqual(['0:10']) - }) - - await act(() => { - render( - h(MarketSection, { - ...initialProps, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`rerender:${pageIndex}:${pageSize}`) - }, - }), - renderedComponent.container, - ) - }) - - expect(calls).toEqual(['0:10']) - }) - - test('stops showing a loading state when a requested question page fails to load', async () => { - const failedPageLoad = createDeferred() - const initialProps = createMarketSectionProps({ - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - if (pageIndex === 1 && pageSize === 10) return await failedPageLoad.promise - }, - zoltarQuestionCount: 12n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 12n, - questions: [createBinaryForkQuestion()], - }, - }) - const renderedComponent = await renderIntoDocument(h(MarketSection, initialProps)) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - const nextPageButton = documentQueries.getByRole('button', { name: 'Next page' }) - await act(() => { - fireEvent.click(nextPageButton) - }) - expect(documentQueries.getByText('Loading questions…')).not.toBeNull() - - void failedPageLoad.promise.catch(() => undefined) - failedPageLoad.reject(new Error('page load failed')) - await waitFor(() => { - expect(documentQueries.queryByText('Loading questions…')).toBeNull() - expect(documentQueries.getByText('Question page unavailable.')).not.toBeNull() - }) - }) - - test('does not auto-load questions while the question count is unresolved', async () => { - const calls: string[] = [] - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - onLoadZoltarQuestionPage: async () => { - calls.push('load') - }, - zoltarQuestionCount: undefined, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - expect(calls).toEqual([]) - }) - - test('does not auto-load questions when the resolved count is zero', async () => { - const calls: string[] = [] - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - onLoadZoltarQuestionPage: async () => { - calls.push('load') - }, - zoltarQuestionCount: 0n, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - expect(calls).toEqual([]) - }) - - test('does not auto-load questions when they are already loaded', async () => { - const calls: string[] = [] - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - onLoadZoltarQuestionPage: async () => { - calls.push('load') - }, - zoltarQuestionCount: 3n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 3n, - questions: [createBinaryForkQuestion()], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - expect(calls).toEqual([]) - }) - - test('reloads loaded questions when the environment refresh key changes', async () => { - const onLoadZoltarQuestionPage = mock(async () => undefined) - const initialProps = createMarketSectionProps({ - onLoadZoltarQuestionPage, - zoltarQuestionCount: 1n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 1n, - questions: [createBinaryForkQuestion()], - }, - }) - const renderedComponent = await renderIntoDocument(h(MarketSection, initialProps)) - cleanupRenderedComponent = renderedComponent.cleanup - - expect(onLoadZoltarQuestionPage).not.toHaveBeenCalled() - - await act(() => { - render(h(MarketSection, { ...initialProps, environmentRefreshKey: 1 }), renderedComponent.container) - }) - - await waitFor(() => { - expect(onLoadZoltarQuestionPage).toHaveBeenCalledTimes(1) - expect(onLoadZoltarQuestionPage).toHaveBeenCalledWith(0, 10) - }) - }) - - test('auto-loads questions once after the count resolves above zero even when the universe is unresolved', async () => { - const calls: string[] = [] - const initialProps = createMarketSectionProps({ - activeUniverseId: 12n, - loadingZoltarQuestionCount: true, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`${pageIndex}:${pageSize}`) - }, - zoltarQuestionCount: undefined, - zoltarUniverse: undefined, - zoltarUniverseState: 'unknown', - }) - - const renderedComponent = await renderIntoDocument(h(MarketSection, initialProps)) - cleanupRenderedComponent = renderedComponent.cleanup - expect(calls).toEqual([]) - - await act(() => { - render( - h( - MarketSection, - createMarketSectionProps({ - ...initialProps, - loadingZoltarQuestionCount: false, - zoltarQuestionCount: 3n, - }), - ), - renderedComponent.container, - ) - }) - - await waitFor(() => { - expect(calls).toEqual(['0:10']) - }) - }) - - test('does not auto-load questions again on rerender when the active universe id is unchanged', async () => { - const calls: string[] = [] - const initialProps = createMarketSectionProps({ - activeUniverseId: 13n, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`${pageIndex}:${pageSize}`) - }, - zoltarQuestionCount: 3n, - zoltarUniverse: undefined, - zoltarUniverseState: 'unknown', - }) - - const renderedComponent = await renderIntoDocument(h(MarketSection, initialProps)) - cleanupRenderedComponent = renderedComponent.cleanup - await waitFor(() => { - expect(calls).toEqual(['0:10']) - }) - - await act(() => { - render( - h( - MarketSection, - createMarketSectionProps({ - ...initialProps, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`rerender:${pageIndex}:${pageSize}`) - }, - }), - ), - renderedComponent.container, - ) - }) - - expect(calls).toEqual(['0:10']) - }) - - test('retries question auto-load when the user leaves and re-enters the failed page', async () => { - const calls: string[] = [] - const initialLoad = createDeferred() - const firstPageQuestion = createBinaryForkQuestion() - const secondPageQuestion = { - ...createBinaryForkQuestion(), - questionId: '0x02', - title: 'Second page question', - } - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeUniverseId: 9n, - loadingZoltarQuestions: false, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`load:${pageIndex}:${pageSize}`) - if (pageIndex === 1 && pageSize === 10) return await initialLoad.promise - }, - zoltarQuestionCount: 12n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 12n, - questions: [firstPageQuestion], - }, - zoltarUniverse: undefined, - zoltarUniverseState: 'unknown', - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - await act(() => { - fireEvent.click(documentQueries.getByRole('button', { name: 'Next page' })) - }) - await act(async () => { - initialLoad.reject(new Error('temporary failure')) - await expect(initialLoad.promise).rejects.toThrow('temporary failure') - }) - await waitFor(() => { - expect(calls).toEqual(['load:1:10']) - }) - - await act(() => { - render( - h( - MarketSection, - createMarketSectionProps({ - activeUniverseId: 9n, - loadingZoltarQuestions: false, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`retry:${pageIndex}:${pageSize}`) - }, - zoltarQuestionCount: 12n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 12n, - questions: [firstPageQuestion], - }, - zoltarUniverse: undefined, - zoltarUniverseState: 'unknown', - }), - ), - renderedComponent.container, - ) - }) - - await act(() => { - fireEvent.click(documentQueries.getByRole('button', { name: 'Previous page' })) - }) - await act(() => { - render( - h( - MarketSection, - createMarketSectionProps({ - activeUniverseId: 9n, - loadingZoltarQuestions: false, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`retry:${pageIndex}:${pageSize}`) - }, - zoltarQuestionCount: 12n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 12n, - questions: [firstPageQuestion], - }, - zoltarUniverse: undefined, - zoltarUniverseState: 'unknown', - }), - ), - renderedComponent.container, - ) - }) - await act(() => { - fireEvent.click(documentQueries.getByRole('button', { name: 'Next page' })) - }) - await act(() => { - render( - h( - MarketSection, - createMarketSectionProps({ - activeUniverseId: 9n, - loadingZoltarQuestions: false, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`retry:${pageIndex}:${pageSize}`) - }, - zoltarQuestionCount: 12n, - zoltarQuestionPage: { - pageIndex: 1, - pageSize: 10, - questionCount: 12n, - questions: [secondPageQuestion], - }, - zoltarUniverse: undefined, - zoltarUniverseState: 'unknown', - }), - ), - renderedComponent.container, - ) - }) - - await waitFor(() => { - expect(calls).toEqual(['load:1:10', 'retry:1:10']) - }) - }) - - test('invalidates stale paged question data when the question count changes', async () => { - const calls: string[] = [] - const initialProps = createMarketSectionProps({ - onLoadZoltarQuestionPage: async () => undefined, - zoltarQuestionCount: 1n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 1n, - questions: [createBinaryForkQuestion()], - }, - }) - const renderedComponent = await renderIntoDocument(h(MarketSection, initialProps)) - cleanupRenderedComponent = renderedComponent.cleanup - - await act(() => { - render( - h( - MarketSection, - createMarketSectionProps({ - ...initialProps, - onLoadZoltarQuestionPage: async (pageIndex, pageSize) => { - calls.push(`${pageIndex}:${pageSize}`) - }, - zoltarQuestionCount: 2n, - }), - ), - renderedComponent.container, - ) - }) - - await waitFor(() => { - expect(calls).toEqual(['0:10']) - }) - }) - - test('shows a not-yet-loaded question state instead of rendering an empty page', async () => { - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - loadingZoltarQuestionCount: false, - loadingZoltarQuestions: false, - onLoadZoltarQuestionPage: async () => undefined, - zoltarQuestionCount: 3n, - zoltarQuestionPage: { - pageIndex: 1, - pageSize: 10, - questionCount: 3n, - questions: [], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - expect(documentQueries.queryByText('No questions were returned for the selected page.')).toBeNull() - expect(documentQueries.getByText('Loading questions…')).not.toBeNull() - }) - - test('opens the create question view from the empty questions state', async () => { - const selectedViews: string[] = [] - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - onActiveViewChange: view => { - selectedViews.push(view) - }, - zoltarQuestionCount: 0n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 0n, - questions: [], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - await act(() => { - fireEvent.click(within(document.body).getByRole('button', { name: 'Create question' })) - }) - - expect(selectedViews).toEqual(['create']) - }) - - test('omits the removed Zoltar question introduction', async () => { - const renderedComponent = await renderIntoDocument(h(MarketSection, createMarketSectionProps())) - cleanupRenderedComponent = renderedComponent.cleanup - - expect(within(document.body).queryByText(/A Question is a reusable resolution definition/)).toBeNull() - }) - - test('selects a paginated question for the fork workflow', async () => { - const selectedQuestionIds: string[] = [] - const selectedViews: string[] = [] - const question = createBinaryForkQuestion() - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - onActiveViewChange: view => { - selectedViews.push(view) - }, - onUseQuestionForFork: questionId => { - selectedQuestionIds.push(questionId) - }, - zoltarQuestionCount: 1n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 1n, - questions: [question], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - const useForForkButton = documentQueries.getByRole('button', { name: 'Use for fork: Fork question title (0x01)' }) - await act(() => { - useForForkButton.dispatchEvent(new window.MouseEvent('click', { bubbles: true })) - }) - - expect(selectedQuestionIds).toEqual([question.questionId]) - expect(selectedViews).toEqual(['fork']) - }) - - test('gives same-titled question actions distinct accessible names', async () => { - const firstQuestion = createBinaryForkQuestion() - const secondQuestion = { ...createBinaryForkQuestion(), questionId: '0x02' } - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - zoltarQuestionCount: 2n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 2n, - questions: [firstQuestion, secondQuestion], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - expect(documentQueries.getByRole('button', { name: 'Use for fork: Fork question title (0x01)' })).not.toBeNull() - expect(documentQueries.getByRole('button', { name: 'Use for fork: Fork question title (0x02)' })).not.toBeNull() - expect(documentQueries.getByRole('button', { name: 'Create pool from question: Fork question title (0x01)' })).not.toBeNull() - expect(documentQueries.getByRole('button', { name: 'Create pool from question: Fork question title (0x02)' })).not.toBeNull() - }) - - test('shows immutable questions without missing-context helper copy', async () => { - const question = createBinaryForkQuestion() - question.description = '' - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - zoltarQuestionCount: 1n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 1n, - questions: [question], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - expect(documentQueries.queryByText('No resolution notes or supporting context provided.')).toBeNull() - expect(documentQueries.queryByText('Add resolution notes, evidence sources, and edge-case handling before users rely on this question.')).toBeNull() - }) - - test('uses a compact compatibility status beside disabled non-binary pool actions', async () => { - const question = { - ...createBinaryForkQuestion(), - marketType: 'categorical' as const, - outcomeLabels: ['Alpha', 'Beta', 'Gamma'], - } - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - zoltarQuestionCount: 1n, - zoltarQuestionPage: { - pageIndex: 0, - pageSize: 10, - questionCount: 1n, - questions: [question], - }, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - const createPoolButton = documentQueries.getByRole('button', { name: 'Create pool from question: Fork question title (0x01)' }) - if (!(createPoolButton instanceof HTMLButtonElement)) throw new Error('Expected create-pool button') - expect(createPoolButton.disabled).toBe(true) - expect(createPoolButton.title).toBe('Binary pools only') - expect(documentQueries.getByText('Binary pools only')).not.toBeNull() - expect(document.body.textContent).not.toContain('Non-binary questions are valid in Zoltar') - }) - - test('does not render redundant universe summary cards for questions view', async () => { - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'questions', - zoltarQuestionCount: 4n, - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - expect(documentQueries.queryByText('Active Root Universe')).toBeNull() - expect(documentQueries.queryByText('The root universe is active and unforked. Question creation and fork preparation remain the primary workflows.')).toBeNull() - expect(documentQueries.queryByText('Forked Universe')).toBeNull() - expect(documentQueries.queryByText('Universe Context')).toBeNull() - }) - - test('opens the fork workflow in a modal instead of rendering it inline', async () => { - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'fork', - zoltarQuestionCount: 2n, - zoltarUniverse: createZoltarUniverse({ forkBurnDivisor: 5n, forkThresholdAttoRep: 100n * ATTO_REP }), - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - expect(documentQueries.queryByRole('dialog')).toBeNull() - expect(document.body.textContent).toContain('Fork Threshold≈ 100.00 REP') - expect(document.body.textContent).toContain('Permanent REP Burn≈ 20.00 REP') - expect(document.body.textContent).not.toContain('Forking selects one existing question') - expect(document.body.textContent).not.toContain('question creation in this universe stops') - const openForkButton = documentQueries.getByRole('button', { name: 'Fork Universe' }) - await act(() => { - openForkButton.dispatchEvent(new window.MouseEvent('click', { bubbles: true })) - }) - const modal = documentQueries.getByRole('dialog') - expect(modal).not.toBeNull() - expect(documentQueries.getAllByText('Fork Universe').length > 0).toBe(true) - expect(within(modal as HTMLElement).queryByText('Confirm transaction context')).toBeNull() - expect(modal.textContent).toContain('QuestionNone selected') - expectTransactionButtonDisabled(modal as HTMLElement, 'Fork Universe', 'Select a valid fork question to continue.') - }) - - test('uses distinct action and dialog title casing for forked-universe details', async () => { - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'fork', - zoltarUniverse: createZoltarUniverse({ - forkQuestionDetails: createBinaryForkQuestion(), - hasForked: true, - }), - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - expect(document.body.textContent).not.toContain('Permanent REP Burn') - expect(document.body.textContent).not.toContain('Fork Threshold') - const launcher = documentQueries.getByRole('button', { name: 'View fork details' }) - await act(() => { - fireEvent.click(launcher) - }) - - const modal = documentQueries.getByRole('dialog', { name: 'View Fork Details' }) - const modalQueries = within(modal as HTMLElement) - expect(modal.textContent).not.toContain('Fork Threshold') - expect(modal.textContent).not.toContain('You Pay') - expect(modal.textContent).not.toContain('Migration Custody Credit') - expect(modal.textContent).not.toContain('Permanent REP Burn') - expect(modalQueries.queryByRole('button', { name: 'Fork Universe' })).toBeNull() - }) - - test('keeps the selected fork question understandable before transaction review', async () => { - const question = createBinaryForkQuestion() - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'fork', - hasLoadedZoltarQuestions: true, - zoltarForkQuestionId: question.questionId, - zoltarQuestionCount: 1n, - zoltarQuestions: [question], - zoltarUniverse: createZoltarUniverse({ universeId: 7n }), - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const summary = document.body.querySelector('.selected-fork-question-summary') - if (!(summary instanceof HTMLElement)) throw new Error('Expected selected fork question summary') - expect(summary.getAttribute('aria-label')).toBe('Selected fork question') - expect(within(summary).getByText('Fork question title')).not.toBeNull() - expect(within(summary).getByText('Binary')).not.toBeNull() - expect(within(summary).getByText('Yes')).not.toBeNull() - expect(within(summary).getByText('No')).not.toBeNull() - expect(within(summary).queryByText('Universe 0x7')).toBeNull() - expect(within(summary).getByRole('button', { name: `Copy identifier ${question.questionId}` })).not.toBeNull() - }) - - test('shows the canonical fork question summary on a fresh visit to a forked universe', async () => { - const question = createBinaryForkQuestion() - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'fork', - zoltarForkQuestionId: '', - zoltarUniverse: createZoltarUniverse({ - forkQuestionDetails: question, - hasForked: true, - universeId: 7n, - }), - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const summary = document.body.querySelector('.selected-fork-question-summary') - if (!(summary instanceof HTMLElement)) throw new Error('Expected canonical fork question summary') - expect(within(summary).getByText('Fork question title')).not.toBeNull() - expect(within(summary).getByText('Binary')).not.toBeNull() - expect(within(summary).getByText('Yes')).not.toBeNull() - expect(within(summary).getByText('No')).not.toBeNull() - expect(within(summary).queryByText('Universe 0x7')).toBeNull() - expect(within(summary).getByRole('button', { name: `Copy identifier ${question.questionId}` })).not.toBeNull() - - const documentQueries = within(document.body) - await act(() => { - fireEvent.click(documentQueries.getByRole('button', { name: 'View fork details' })) - }) - const modal = documentQueries.getByRole('dialog', { name: 'View Fork Details' }) - const modalQueries = within(modal) - const questionIdInput = modalQueries.getByLabelText('Fork Question ID') as HTMLInputElement - expect(questionIdInput.value).toBe(question.questionId) - expect(questionIdInput.disabled).toBe(true) - expect(modalQueries.getByText('Fork question title')).not.toBeNull() - expect(modal.textContent).toContain('Yes') - expect(modal.textContent).toContain('No') - }) - - test('uses the forked universe canonical question across summary and modal after a universe transition', async () => { - const localQuestion = createBinaryForkQuestion() - const canonicalQuestion = { - ...createBinaryForkQuestion(), - questionId: '0x02', - title: 'Canonical fork question', - } - const initialProps = createMarketSectionProps({ - activeView: 'fork', - zoltarForkQuestionId: localQuestion.questionId, - zoltarQuestionCount: 1n, - zoltarQuestions: [localQuestion], - zoltarUniverse: createZoltarUniverse({ universeId: 1n }), - }) - const renderedComponent = await renderIntoDocument(h(MarketSection, initialProps)) - cleanupRenderedComponent = renderedComponent.cleanup - - await act(() => { - render( - h( - MarketSection, - createMarketSectionProps({ - ...initialProps, - activeUniverseId: 2n, - zoltarUniverse: createZoltarUniverse({ - forkQuestionDetails: canonicalQuestion, - hasForked: true, - universeId: 2n, - }), - }), - ), - renderedComponent.container, - ) - }) - - const summary = document.body.querySelector('.selected-fork-question-summary') - if (!(summary instanceof HTMLElement)) throw new Error('Expected transitioned fork question summary') - const summaryQueries = within(summary) - expect(summaryQueries.getByText('Canonical fork question')).not.toBeNull() - expect(summaryQueries.queryByText('Fork question title')).toBeNull() - expect(summaryQueries.queryByText('Universe 0x2')).toBeNull() - expect(summaryQueries.getByRole('button', { name: `Copy identifier ${canonicalQuestion.questionId}` })).not.toBeNull() - - const documentQueries = within(document.body) - await act(() => { - fireEvent.click(documentQueries.getByRole('button', { name: 'View fork details' })) - }) - const modalQueries = within(documentQueries.getByRole('dialog', { name: 'View Fork Details' })) - expect((modalQueries.getByLabelText('Fork Question ID') as HTMLInputElement).value).toBe(canonicalQuestion.questionId) - expect(modalQueries.getByText('Canonical fork question')).not.toBeNull() - expect(modalQueries.queryByText('Fork question title')).toBeNull() - }) - - test('keeps every required fork-question field visible while selected metadata is unavailable', async () => { - const questionId = '0x99' - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'fork', - zoltarForkQuestionId: questionId, - zoltarQuestionCount: 1n, - zoltarQuestions: [], - zoltarUniverse: createZoltarUniverse({ universeId: 7n }), - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const summary = document.body.querySelector('.selected-fork-question-summary') - if (!(summary instanceof HTMLElement)) throw new Error('Expected fallback fork question summary') - const summaryQueries = within(summary) - expect(summaryQueries.getByText('Title')).not.toBeNull() - expect(summaryQueries.getByText('Outcomes')).not.toBeNull() - expect(summaryQueries.getByText('Question Type')).not.toBeNull() - expect(summaryQueries.getAllByText('Unavailable')).toHaveLength(3) - expect(summaryQueries.queryByText('Universe 0x7')).toBeNull() - expect(summaryQueries.getByRole('button', { name: `Copy identifier ${questionId}` })).not.toBeNull() - }) - - test('allows entering an existing question ID without requiring question creation', async () => { - const onZoltarForkQuestionIdChange = mock(() => undefined) - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'fork', - hasLoadedZoltarQuestions: true, - loadingZoltarQuestionCount: false, - loadingZoltarQuestions: false, - onZoltarForkQuestionIdChange, - zoltarQuestionCount: 0n, - zoltarQuestions: [], - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - const forkButton = documentQueries.getByRole('button', { name: 'Fork Universe' }) as HTMLButtonElement - expect(forkButton.disabled).toBe(false) - expect(documentQueries.queryByRole('button', { name: 'Create question' })).toBeNull() - expect(documentQueries.queryByRole('dialog')).toBeNull() - - await act(() => { - fireEvent.click(forkButton) - }) - const modal = documentQueries.getByRole('dialog') - const questionIdInput = within(modal as HTMLElement).getByLabelText('Fork Question ID') as HTMLInputElement - expect(questionIdInput.disabled).toBe(false) - fireEvent.input(questionIdInput, { target: { value: '0x99' } }) - expect(onZoltarForkQuestionIdChange).toHaveBeenCalledWith('0x99') - expect(questionIdInput.value).toBe('0x99') - }) - - test('loads fork metadata for an exact question ID from an initially empty question list', async () => { - const question = { ...createBinaryForkQuestion(), questionId: '0x99', title: 'Directly loaded fork question' } - const loadCalls: string[] = [] - function Harness() { - const [questionId, setQuestionId] = useState('') - const [questions, setQuestions] = useState[]>([]) - const [lookupId, setLookupId] = useState(undefined) - const [lookupError, setLookupError] = useState(undefined) - return ( - - { - loadCalls.push(id) - setLookupId(id) - if (loadCalls.length === 1) { - setLookupError('Question lookup temporarily failed') - return - } - setLookupError(undefined) - setQuestions([question]) - }, - onZoltarForkQuestionIdChange: setQuestionId, - zoltarForkQuestionId: questionId, - zoltarForkApproval: { error: undefined, loading: false, value: 100n }, - zoltarForkRepBalanceAttoRep: 100n, - zoltarQuestionCount: 1n, - zoltarQuestionLookupError: lookupError, - zoltarQuestionLookupId: lookupId, - zoltarQuestions: questions, - zoltarUniverse: createZoltarUniverse({ forkBurnDivisor: 5n, zoltarAddress: '0x00000000000000000000000000000000000000a1' }), - })} - /> - - ) - } - const renderedComponent = await renderIntoDocument() - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - await act(() => { - fireEvent.click(documentQueries.getByRole('button', { name: 'Fork Universe' })) - }) - const modalQueries = within(documentQueries.getByRole('dialog')) - await act(() => { - fireEvent.input(modalQueries.getByLabelText('Fork Question ID'), { target: { value: '0x099' } }) - }) - - await waitFor(() => { - expect(loadCalls).toEqual(['0x99']) - expect(modalQueries.getByText('Question lookup temporarily failed')).not.toBeNull() - }) - await act(() => { - fireEvent.click(modalQueries.getByRole('button', { name: 'Retry' })) - }) - await waitFor(() => { - expect(loadCalls).toEqual(['0x99', '0x99']) - expect(modalQueries.getByText('Directly loaded fork question')).not.toBeNull() - }) - const confirmationInput = modalQueries.getByLabelText('Type FORK to confirm') as HTMLInputElement - expect(confirmationInput.disabled).toBe(false) - await act(() => { - fireEvent.input(confirmationInput, { target: { value: 'FORK' } }) - }) - expect((modalQueries.getByRole('button', { name: 'Fork Universe' }) as HTMLButtonElement).disabled).toBe(false) - }) - - test('opens root-universe child-universe deployment in a modal', async () => { - let createChildUniverseCallCount = 0 - const renderedComponent = await renderIntoDocument( - h( - MarketSection, - createMarketSectionProps({ - activeView: 'questions', - onCreateChildUniverseForOutcomeIndex: () => { - createChildUniverseCallCount += 1 - }, - zoltarUniverse: createZoltarUniverse({ - childUniverses: [ - { - exists: false, - forkTime: 1n, - outcomeIndex: 1n, - outcomeLabel: 'Yes', - parentUniverseId: 1n, - reputationToken: zeroAddress, - universeId: 2n, - }, - ], - forkQuestionDetails: createBinaryForkQuestion(), - hasForked: true, - }), - }), - ), - ) - cleanupRenderedComponent = renderedComponent.cleanup - - const documentQueries = within(document.body) - fireEvent.click(documentQueries.getByRole('button', { name: 'Create child universe' })) - const modal = await waitFor(() => documentQueries.getByRole('dialog')) - const modalQueries = within(modal) - expect(modalQueries.getByText('Create Child Universe')).not.toBeNull() - expect(modalQueries.getByText('Selected Child Universe')).not.toBeNull() - - fireEvent.click(modalQueries.getByRole('button', { name: 'Deploy universe' })) - expect(createChildUniverseCallCount).toBe(1) - }) -}) diff --git a/ui/ts/tests/simulation/simulationScenarios.test.ts b/ui/ts/tests/simulation/simulationScenarios.test.ts deleted file mode 100644 index 2b39f5583..000000000 --- a/ui/ts/tests/simulation/simulationScenarios.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/// - -import { describe, expect, test } from 'bun:test' -import { getSimulationScenarioDescription, getSimulationScenarioLabel, normalizeSimulationScenario } from '../../simulation/scenarios.js' - -void describe('simulation scenarios', () => { - void test('normalizes the securitypoolx2 scenario', () => { - expect(normalizeSimulationScenario('securitypoolx2')).toBe('securitypoolx2') - expect(normalizeSimulationScenario('securitypoolx2-auction')).toBe('securitypoolx2-auction') - }) - - void test('returns the securitypoolx2 scenario label', () => { - expect(getSimulationScenarioLabel('securitypoolx2')).toBe('Security pool x2') - expect(getSimulationScenarioLabel('securitypoolx2-auction')).toBe('Security pool x2 auction') - }) - - void test('returns descriptions for each scenario', () => { - expect(getSimulationScenarioDescription('baseline')).toBe('Fresh walletless simulation with funded QA accounts and no app contracts deployed. Use it to test the Deploy flow from scratch.') - expect(getSimulationScenarioDescription('deployed')).toBe('App contracts are deployed, but no security pools or questions are created. Use it to test setup flows from an empty deployment.') - expect(getSimulationScenarioDescription('security-pool')).toBe('One seeded question, one security pool, and one funded vault with an active capacity ownership. Use it to test pool actions and liquidation paths.') - expect(getSimulationScenarioDescription('securitypoolx2')).toBe('Two seeded questions with two security pools and two funded vaults in each pool. Use it to test multi-pool selection and repeated pool actions.') - expect(getSimulationScenarioDescription('securitypoolx2-auction')).toBe('Two seeded questions with one own-escalation fork already triggered and one child truth auction seeded with ten bids. Use it to test the fork-auction bidbook and settlement actions.') - }) -}) diff --git a/ui/ts/types/app.ts b/ui/ts/types/app.ts deleted file mode 100644 index abeeba9af..000000000 --- a/ui/ts/types/app.ts +++ /dev/null @@ -1,129 +0,0 @@ -import type { MarketType, ReportingOutcomeKey } from './contracts.js' -import type { Address, Hash } from '@zoltar/shared/ethereum' -import type { GlobalTransactionPresentation, TransactionIntent } from './components.js' -import type { TransactionRequestPreview } from '../lib/chainBackend.js' - -export type Route = 'deploy' | 'zoltar' | 'security-pools' | 'open-oracle' | 'not-found' - -export type RefreshStateOptions = { - loadChainClock?: boolean - loadDeploymentState?: boolean - loadWalletState?: boolean -} - -type RefreshState = (options?: RefreshStateOptions) => Promise - -export type WriteOperationsParameters = { - accountAddress: Address | undefined - onTransactionCanceled?: () => void - onTransactionFailed?: (message: string) => void - onTransactionFinished: () => void - onTransactionPresented: (presentation: GlobalTransactionPresentation) => void - onTransactionPrepared?: (preview: TransactionRequestPreview) => void - onTransactionRequested: (intent: TransactionIntent) => void - onTransactionSubmitted: (hash: Hash) => void - refreshState: RefreshState -} - -export type AccountState = { - address: Address | undefined - chainId: string | undefined - ethBalanceAttoEth: bigint | undefined - wethBalanceAttoEth: bigint | undefined -} - -export type MarketFormState = { - answerUnit: string - categoricalOutcomes: string[] - description: string - scalarIncrement: string - scalarMax: string - scalarMin: string - title: string - endTime: string - marketType: MarketType - startTime: string -} - -export type SecurityPoolFormState = { - initialReportPriorityFeeGwei: string - marketId: string - statoblastSecurityMultiplierBps: string -} - -export type SecurityVaultFormState = { - depositAmount: string - targetHealthFactor: string - repWithdrawAmount: string - selectedVaultOwner: string - securityPoolAddress: string - stagedOperationTimeoutMinutes?: string -} - -export type OpenOracleFormState = { - amount1: string - amount2: string - disputeNewAmount1: string - disputeNewAmount2: string - disputeTokenToSwap: 'token1' | 'token2' - reportId: string - stateHash: string -} - -export type OpenOracleCreateFormState = { - ethValue: string - exactToken1Report: string - initialToken2Amount: string - escalationHalt: string - feePercentage: string - multiplier: string - protocolFee: string - settlementTime: string - settlerRewardEthAmount: string - token1Address: string - token2Address: string - disputeDelay: string -} - -export type TradingFormState = { - completeSetAmount: string - redeemAmount: string - securityPoolAddress: string - selectedShareOutcome: ReportingOutcomeKey - targetOutcomeIndexes: string -} - -export type ReportingWithdrawDepositIndexesByOutcome = { - invalid: bigint[] - yes: bigint[] - no: bigint[] -} - -export type ReportingFormState = { - reportAmount: string - securityPoolAddress: string - selectedOutcome: ReportingOutcomeKey | undefined - selectedWithdrawDepositIndexesByOutcome: ReportingWithdrawDepositIndexesByOutcome -} - -export type ForkAuctionFormState = { - claimBidIndex: string - claimBidTick: string - depositIndexes: string - directForkQuestionId: string - directForkUniverseId: string - refundBidIndex: string - refundTick: string - repMigrationOutcomes: string - securityPoolAddress: string - selectedOutcome: ReportingOutcomeKey - settlementAddress: string - submitBidAmount: string - submitBidPrice: string - vaultAddress: string -} - -export type ZoltarMigrationFormState = { - amount: string - outcomeIndexes: string -} diff --git a/ui/tsconfig.json b/ui/tsconfig.base.json similarity index 68% rename from ui/tsconfig.json rename to ui/tsconfig.base.json index 795deb765..0a6b05d06 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.base.json @@ -3,12 +3,6 @@ "target": "ESNext", "module": "ES2022", "moduleResolution": "bundler", - "baseUrl": ".", - "paths": { - "@zoltar/shared/*": ["../shared/js/*"] - }, - "rootDir": "./ts", - "outDir": "./js", "sourceMap": true, "inlineSources": true, "declaration": true, @@ -25,14 +19,10 @@ "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, - "jsx": "react-jsx", "jsxImportSource": "preact", "lib": ["ESNext", "DOM"], "typeRoots": ["../node_modules/@types"], - // add any @types here, empty array instead of default to prevent @types/node from being picked up "types": [] - }, - "include": ["./ts/**/*.ts", "./ts/**/*.tsx"], - "exclude": ["./ts/tests/**/*.ts", "./ts/tests/**/*.tsx"] + } } diff --git a/ui/tsconfig.vendor.json b/ui/tsconfig.vendor.json deleted file mode 100644 index c863dc3c2..000000000 --- a/ui/tsconfig.vendor.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "rootDir": "./build", - "inlineSourceMap": true, - "declaration": false, - - "strict": true, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "exactOptionalPropertyTypes": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": true, - "noUncheckedIndexedAccess": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - - "lib": [ "ES2022" ], - "typeRoots": [ "../node_modules/@types" ], - "types": [ "bun-types" ], - }, - "include": [ - "./build/*.mts" - ], -} diff --git a/ui/bun.lock b/ui/zoltar/bun.lock similarity index 74% rename from ui/bun.lock rename to ui/zoltar/bun.lock index 21588bc0a..ceb5a4041 100644 --- a/ui/bun.lock +++ b/ui/zoltar/bun.lock @@ -1,13 +1,15 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { + "name": "@zoltar/ui-zoltar", "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", - "@zoltar/shared": "file:../shared", + "@zoltar/shared": "file:../../shared", + "@zoltar/ui-core-shared": "file:../coreShared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", @@ -24,6 +26,9 @@ }, }, "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", "brace-expansion": "5.0.9", "fast-uri": "3.1.5", "nanoid": "3.3.18", @@ -32,7 +37,7 @@ "ws": "8.21.0", }, "packages": { - "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], + "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], "@ark/schema": ["@ark/schema@0.3.3", "", { "dependencies": { "@ark/util": "0.2.2" } }, "sha512-SA4QggzaKHxkNB+faWmhSJbCPMHmGBCpb6dNG3VIt5VOMZhZJJSD76/tOUzQvvJNzztHkTakrTZea+iKsEDcRQ=="], @@ -40,81 +45,75 @@ "@effect/schema": ["@effect/schema@0.75.5", "", { "dependencies": { "fast-check": "^3.21.0" }, "peerDependencies": { "effect": "^3.9.2" } }, "sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw=="], - "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + "@ethereumjs/binarytree": ["@ethereumjs/binarytree@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-szugxzYrQHRvlhbu0BrcW/Kw7tg3K0b1HbKCdwRrFhvz80RbGfML2wqDDZhO5NyvHqWpS+Rt9q+qV9LxOHCP6w=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + "@ethereumjs/common": ["@ethereumjs/common@10.1.2", "", { "dependencies": { "@ethereumjs/util": "^10.1.2", "eventemitter3": "^5.0.1" } }, "sha512-whWnhqAxwpDy4zWkM6rqMzb8nioZJpiys01N57+HDyanvK5IRzodV5tdMRDt66PD5vDjl2c9K5UcB039gU2Oyw=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@ethereumjs/evm": ["@ethereumjs/evm@10.1.2", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.2", "@ethereumjs/common": "^10.1.2", "@ethereumjs/statemanager": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "eventemitter3": "^5.0.1" } }, "sha512-yIslek2pcsUQwBJFeeamN529hbNYBtrSuneSCMn4AM71WAx8vntgDbzYoAJsG/whyB9wOVvzHXubju1JXmaEag=="], - "@ethereumjs/binarytree": ["@ethereumjs/binarytree@10.1.1", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.1", "@ethereumjs/util": "^10.1.1", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-vcU+cDgWJ0YNrJb5gWcKWfMpYJg4W+8benAds7QQo2ayTYZbuMqju3Fq7m5LNG+xqkdF0a901QDbT5xaB7rJLQ=="], - - "@ethereumjs/common": ["@ethereumjs/common@10.1.1", "", { "dependencies": { "@ethereumjs/util": "^10.1.1", "eventemitter3": "^5.0.1" } }, "sha512-NefPzPlrJ9w+NWVe06P+sHZQU98E1AEU9vhiHJEVT2wEcNBC1YX6hON9+smrfbn86C4U1pb2zbvjhkF+n/LKBw=="], - - "@ethereumjs/evm": ["@ethereumjs/evm@10.1.1", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.1", "@ethereumjs/common": "^10.1.1", "@ethereumjs/statemanager": "^10.1.1", "@ethereumjs/util": "^10.1.1", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "eventemitter3": "^5.0.1" } }, "sha512-L6mVKbEe1bvvxWilUWfzkkYb+5bCWIlq32Lzuv4MJf4MNzZZ++P72/WIsKoLntbxWEuZDcYXGwHhmM9pfbu1BA=="], - - "@ethereumjs/mpt": ["@ethereumjs/mpt@10.1.1", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.1", "@ethereumjs/util": "^10.1.1", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-Kh1vNhUwSIyuehOJk9Hqxzgu8D5B2L3jidKVaH4VB/dSo/6mVA0IKQ312c0ophXY8uU4f8NOTW602FJe6Ezaag=="], + "@ethereumjs/mpt": ["@ethereumjs/mpt@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-dBlXpkP1ssp+AcUxsJUrY72LZuE1JQEp1AZn5mgmbBcd2Gwkpyi57q4zATlZbxrZUxp/K9UIidgqxQWcOCbo5g=="], "@ethereumjs/rlp": ["@ethereumjs/rlp@5.0.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA=="], - "@ethereumjs/statemanager": ["@ethereumjs/statemanager@10.1.1", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.1", "@ethereumjs/common": "^10.1.1", "@ethereumjs/mpt": "^10.1.1", "@ethereumjs/rlp": "^10.1.1", "@ethereumjs/util": "^10.1.1", "@js-sdsl/ordered-map": "^4.4.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-V7BZhRP8lcP9A1cVclTPWvqyVUmGDcISPYXBFyLVvQQnzTydG8MdSRB+uUuGA0gAyB+jfSJIqkbYNR1U8R7t9Q=="], + "@ethereumjs/statemanager": ["@ethereumjs/statemanager@10.1.2", "", { "dependencies": { "@ethereumjs/binarytree": "^10.1.2", "@ethereumjs/common": "^10.1.2", "@ethereumjs/mpt": "^10.1.2", "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@js-sdsl/ordered-map": "^4.4.2", "@noble/hashes": "^2.0.1", "debug": "^4.4.0", "lru-cache": "11.0.2" } }, "sha512-Pkm9VHF19wxT5c0jB+IDiQBv4W42o0wYSS1NLXeRea0D6CDBu0CLo9ihhnQgM368zXPl6JRbLS+YiG8tJkz4aA=="], "@ethereumjs/trie": ["@ethereumjs/trie@6.2.1", "", { "dependencies": { "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", "@types/readable-stream": "^2.3.13", "debug": "^4.3.4", "ethereum-cryptography": "^2.2.1", "lru-cache": "10.1.0", "readable-stream": "^3.6.0" } }, "sha512-MguABMVi/dPtgagK+SuY57rpXFP+Ghr2x+pBDy+e3VmMqUY+WGzFu1QWjBb5/iJ7lINk4CI2Uwsih07Nu9sTSg=="], - "@ethereumjs/tx": ["@ethereumjs/tx@10.1.1", "", { "dependencies": { "@ethereumjs/common": "^10.1.1", "@ethereumjs/rlp": "^10.1.1", "@ethereumjs/util": "^10.1.1", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-Kz8GWIKQjEQB60ko9hsYDX3rZMHZZOTcmm6OFl855Lu3padVnf5ZactUKM6nmWPsumHED5bWDjO32novZd1zyw=="], + "@ethereumjs/tx": ["@ethereumjs/tx@10.1.2", "", { "dependencies": { "@ethereumjs/common": "^10.1.2", "@ethereumjs/rlp": "^10.1.2", "@ethereumjs/util": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-bAYK3YaYkk+auzxGfZSRVDbLHvboJNTx8/tV6jaqgPVlrA1QKEEADDEp/EGz+KI4NQmTGxEtXZ8tV/WjniRNww=="], - "@ethereumjs/util": ["@ethereumjs/util@10.1.1", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.1", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-r2EhaeEmLZXVs1dT2HJFQysAkr63ZWATu/9tgYSp1IlvjvwyC++DLg5kCDwMM49HBq3sOAhrPnXkoqf9DV2gbw=="], + "@ethereumjs/util": ["@ethereumjs/util@10.1.2", "", { "dependencies": { "@ethereumjs/rlp": "^10.1.2", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1" } }, "sha512-UPBgXtHHfQugoXOSAoeG3jdmPbl37cwV9y3XqTPAnw8tJj8np14TPV2uc5lOs7C2LMF9Ubn66zyaiYxgwGppng=="], "@evmts/zevm": ["@evmts/zevm@0.0.0", "", { "dependencies": { "@ethereumjs/common": "^10.1.1", "@ethereumjs/evm": "^10.1.1", "@ethereumjs/trie": "6.2.1", "@ethereumjs/tx": "^10.1.1", "@noble/curves": "^2.2.0", "@noble/hashes": "^2.2.0" }, "optionalDependencies": { "@evmts/zevm-darwin-arm64": "0.0.0", "@evmts/zevm-darwin-x64": "0.0.0", "@evmts/zevm-freebsd-arm64": "0.0.0", "@evmts/zevm-freebsd-x64": "0.0.0", "@evmts/zevm-linux-arm64-gnu": "0.0.0", "@evmts/zevm-linux-arm64-musl": "0.0.0", "@evmts/zevm-linux-x64-gnu": "0.0.0", "@evmts/zevm-linux-x64-musl": "0.0.0", "@evmts/zevm-win32-arm64-msvc": "0.0.0", "@evmts/zevm-win32-ia32-msvc": "0.0.0", "@evmts/zevm-win32-x64-msvc": "0.0.0" } }, "sha512-YQgEGcHaVSHn1zYk6i3sPbO00UR/2XiBP4IKn2Qj0QWBEkjQZlPCOvxXtxqVF/HolPRKu5OxntcbTwfmivwc5Q=="], @@ -154,8 +153,6 @@ "@latticexyz/world": ["@latticexyz/world@2.2.23", "", { "dependencies": { "@ark/util": "0.2.2", "@latticexyz/block-logs-stream": "2.2.23", "@latticexyz/common": "2.2.23", "@latticexyz/config": "2.2.23", "@latticexyz/protocol-parser": "2.2.23", "@latticexyz/schema-type": "2.2.23", "@latticexyz/store": "2.2.23", "abitype": "1.0.9", "arktype": "2.0.0-beta.6", "debug": "^4.3.4" }, "peerDependencies": { "viem": "2.x" }, "optionalPeers": ["viem"] }, "sha512-H0MBsUFpim/zwfZbUfon5uadexzFVJlMiCS6ER9m8L1U4WY0iIEYFmT7EUkQK+NAxbF12119v1+GdW3pPBCmQA=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="], - "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], "@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="], @@ -164,7 +161,7 @@ "@openzeppelin/contracts": ["@openzeppelin/contracts@5.6.1", "", {}, "sha512-Ly6SlsVJ3mj+b18W3R8gNufB7dTICT105fJhodGAGgyC2oqnBAhqSiNDJ8V8DLY05cCz81GLI0CU5vNYA1EC/w=="], - "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + "@oxc-project/types": ["@oxc-project/types@0.144.0", "", {}, "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg=="], "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], @@ -172,37 +169,35 @@ "@preact/signals": ["@preact/signals@2.0.1", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-f9p/utMgttPb9bJ+UgRBkTDZ9uQiZfX1/gV3pXGWz+yGNQj8MrnG55Xo8MAG4IHcb5UXQO6tvt9ZlsM4A2j+Rw=="], - "@preact/signals-core": ["@preact/signals-core@1.8.0", "", {}, "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA=="], + "@preact/signals-core": ["@preact/signals-core@1.14.4", "", {}, "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.4", "", { "os": "none", "cpu": "arm64" }, "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="], - - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], @@ -312,21 +307,15 @@ "@tevm/webpack-plugin": ["@tevm/webpack-plugin@1.0.0-rc.151", "", { "dependencies": { "@tevm/unplugin": "^1.0.0-rc.151" }, "peerDependencies": { "webpack": ">5.0.0" } }, "sha512-6/3IMmTU932xIrp9oaQZDM/7pQIbfipxSpwjq1rLxy/d0RUOiWiaohmy0vvJBl0s4rRnT+nT5rTn4fJ+lCN04Q=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], - "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], - "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], - - "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], + "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], "@types/readable-stream": ["@types/readable-stream@2.3.15", "", { "dependencies": { "@types/node": "*", "safe-buffer": "~5.1.1" } }, "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ=="], @@ -366,19 +355,19 @@ "@typescript/lib-webworker": ["@better-typescript-lib/webworker@2.11.0", "", { "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-9jdYsSj7YWGixeVok7xZSV+OHpOxMBEcuw+zPHj2462LS812Xq2Vs5Kk00PoFjVXolgQm6naXDG1Wa+x445BPQ=="], - "@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="], + "@vitest/expect": ["@vitest/expect@4.1.11", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw=="], - "@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="], + "@vitest/mocker": ["@vitest/mocker@4.1.11", "", { "dependencies": { "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.9", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="], - "@vitest/runner": ["@vitest/runner@4.1.9", "", { "dependencies": { "@vitest/utils": "4.1.9", "pathe": "^2.0.3" } }, "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg=="], + "@vitest/runner": ["@vitest/runner@4.1.11", "", { "dependencies": { "@vitest/utils": "4.1.11", "pathe": "^2.0.3" } }, "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw=="], - "@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="], + "@vitest/snapshot": ["@vitest/snapshot@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog=="], - "@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="], + "@vitest/spy": ["@vitest/spy@4.1.11", "", {}, "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA=="], - "@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="], + "@vitest/utils": ["@vitest/utils@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ=="], "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], @@ -414,13 +403,13 @@ "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], - "@zoltar/shared": ["@zoltar/shared@file:../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + "@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], - "abitype": ["abitype@1.2.4", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg=="], + "@zoltar/ui-core-shared": ["@zoltar/ui-core-shared@file:../coreShared", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@preact/signals": "2.0.1", "@zoltar/shared": "file:../../shared", "buffer": "6.0.3", "micro-eth-signer": "0.18.1", "micro-packed": "0.8.0", "preact": "10.26.4", "source-map": "0.6.1", "tevm": "1.0.0-rc.151" }, "devDependencies": { "better-typescript-lib": "2.11.0", "bun-types": "1.3.11", "funtypes": "5.1.1", "typescript": "5.8.2" } }], - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "abitype": ["abitype@1.3.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-fk6Te+bojIFrMvMZrnOO+SxCB+RUksTGOzq/60ZRvs1L+BVzvi2bqt9L3W/17ZLdZsyM1FuYf65P5nlmoiH1Bg=="], - "acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="], + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], @@ -442,13 +431,13 @@ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "axios": ["axios@1.18.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw=="], + "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA=="], "better-typescript-lib": ["better-typescript-lib@2.11.0", "", { "dependencies": { "@typescript/lib-decorators": "npm:@better-typescript-lib/decorators@2.11.0", "@typescript/lib-dom": "npm:@better-typescript-lib/dom@2.11.0", "@typescript/lib-es2015": "npm:@better-typescript-lib/es2015@2.11.0", "@typescript/lib-es2016": "npm:@better-typescript-lib/es2016@2.11.0", "@typescript/lib-es2017": "npm:@better-typescript-lib/es2017@2.11.0", "@typescript/lib-es2018": "npm:@better-typescript-lib/es2018@2.11.0", "@typescript/lib-es2019": "npm:@better-typescript-lib/es2019@2.11.0", "@typescript/lib-es2020": "npm:@better-typescript-lib/es2020@2.11.0", "@typescript/lib-es2021": "npm:@better-typescript-lib/es2021@2.11.0", "@typescript/lib-es2022": "npm:@better-typescript-lib/es2022@2.11.0", "@typescript/lib-es2023": "npm:@better-typescript-lib/es2023@2.11.0", "@typescript/lib-es2024": "npm:@better-typescript-lib/es2024@2.11.0", "@typescript/lib-es5": "npm:@better-typescript-lib/es5@2.11.0", "@typescript/lib-es6": "npm:@better-typescript-lib/es6@2.11.0", "@typescript/lib-esnext": "npm:@better-typescript-lib/esnext@2.11.0", "@typescript/lib-scripthost": "npm:@better-typescript-lib/scripthost@2.11.0", "@typescript/lib-webworker": "npm:@better-typescript-lib/webworker@2.11.0" }, "peerDependencies": { "typescript": ">=4.5.2" } }, "sha512-dwaPodcG5lVepyeoEmwRSpRqKWOso5CRktnMcPKlE4jjcHLGjkBhZXIMe6EFD9ALG/Ru3kwOO1t6TNNQ6Pf1Jw=="], @@ -456,7 +445,7 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], @@ -470,7 +459,7 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], @@ -486,7 +475,7 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], @@ -502,21 +491,21 @@ "effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="], - "electron-to-chromium": ["electron-to-chromium@1.5.349", "", {}, "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A=="], + "electron-to-chromium": ["electron-to-chromium@1.5.411", "", {}, "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg=="], - "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], + "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -538,7 +527,7 @@ "expand-tilde": ["expand-tilde@2.0.2", "", { "dependencies": { "homedir-polyfill": "^1.0.1" } }, "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw=="], - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], @@ -556,13 +545,13 @@ "findup-sync": ["findup-sync@5.0.0", "", { "dependencies": { "detect-file": "^1.0.0", "is-glob": "^4.0.3", "micromatch": "^4.0.4", "resolve-dir": "^1.0.1" } }, "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ=="], - "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], - "fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="], + "fs-extra": ["fs-extra@11.4.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -580,8 +569,6 @@ "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], - "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], - "global-modules": ["global-modules@1.0.0", "", { "dependencies": { "global-prefix": "^1.0.1", "is-windows": "^1.0.1", "resolve-dir": "^1.0.0" } }, "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg=="], "global-prefix": ["global-prefix@1.0.2", "", { "dependencies": { "expand-tilde": "^2.0.2", "homedir-polyfill": "^1.0.1", "ini": "^1.3.4", "is-windows": "^1.0.1", "which": "^1.2.14" } }, "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg=="], @@ -598,7 +585,7 @@ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], "homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="], @@ -616,7 +603,7 @@ "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], @@ -656,35 +643,33 @@ "kzg-wasm": ["kzg-wasm@1.0.0", "", {}, "sha512-jMvFK5uiV41BgcLrCaHbNeDeQeZdoTcqOYm7yS1Xdvl4MAykjou+SLlnt+P1bWz5C0YWHOmgUUjIpCuU83zNTA=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - - "loader-runner": ["loader-runner@4.3.2", "", {}, "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], "locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], - "lru-cache": ["lru-cache@11.3.5", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="], + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -704,7 +689,9 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "minimizer-webpack-plugin": ["minimizer-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "@minify-html/node": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "lightningcss": "*", "postcss": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "lightningcss", "postcss", "uglify-js"] }, "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], @@ -714,15 +701,15 @@ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], - "obug": ["obug@2.1.3", "", {}, "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg=="], + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], - "ox": ["ox@0.14.29", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg=="], + "ox": ["ox@0.14.34", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-12seOIk7dv8eAoGQhcWaeKZxNz304IVcDvb9U5Y7JZAEVe21Nm1YMxLjhWah+su5BD4Omx4Zz0z5x3ij9M4GYQ=="], "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], @@ -750,7 +737,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], @@ -770,7 +757,7 @@ "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], - "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + "process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="], "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], @@ -790,7 +777,7 @@ "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], - "rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="], + "rolldown": ["rolldown@1.2.4", "", { "dependencies": { "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.4", "@rolldown/binding-darwin-arm64": "1.2.4", "@rolldown/binding-darwin-x64": "1.2.4", "@rolldown/binding-freebsd-x64": "1.2.4", "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", "@rolldown/binding-linux-arm64-gnu": "1.2.4", "@rolldown/binding-linux-arm64-musl": "1.2.4", "@rolldown/binding-linux-ppc64-gnu": "1.2.4", "@rolldown/binding-linux-s390x-gnu": "1.2.4", "@rolldown/binding-linux-x64-gnu": "1.2.4", "@rolldown/binding-linux-x64-musl": "1.2.4", "@rolldown/binding-openharmony-arm64": "1.2.4", "@rolldown/binding-win32-arm64-msvc": "1.2.4", "@rolldown/binding-win32-x64-msvc": "1.2.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w=="], "rxjs": ["rxjs@7.5.5", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw=="], @@ -814,7 +801,7 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], + "solc": ["solc@0.8.36", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-cnDhjWo8dC5FxnEwCrWxUsJS7RPsHf+pVUe9Tr/+b15sQ40WCVn4vX4R+tOvN5gRfEgiquvFzfF4VV0kSnknhw=="], "solc-typed-ast": ["solc-typed-ast@18.2.6", "", { "dependencies": { "axios": "^1.6.8", "commander": "^12.0.0", "decimal.js": "^10.4.3", "findup-sync": "^5.0.0", "fs-extra": "^11.2.0", "jsel": "^1.1.6", "semver": "^7.6.0", "solc": "0.8.25", "src-location": "^1.1.0", "web3-eth-abi": "^4.2.0" }, "bin": { "sol-ast-compile": "dist/bin/compile.js" } }, "sha512-JObIRpPYO4zvQkAgr3koBr5Du+IkYzVNofykZpGv/44iJ0VRsaEq/X+LrrFD0SDqFcnyYcBOPWDiOXwNOStKlg=="], @@ -836,7 +823,7 @@ "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="], "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], @@ -848,9 +835,7 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "terser": ["terser@5.46.2", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw=="], - - "terser-webpack-plugin": ["terser-webpack-plugin@5.5.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA=="], + "terser": ["terser@5.50.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w=="], "tevm": ["tevm@1.0.0-rc.151", "", { "dependencies": { "@tevm/actions": "^1.0.0-rc.151", "@tevm/address": "^1.0.0-rc.151", "@tevm/base-bundler": "^1.0.0-rc.151", "@tevm/block": "^1.0.0-rc.151", "@tevm/blockchain": "^1.0.0-rc.151", "@tevm/bun-plugin": "^1.0.0-rc.151", "@tevm/bundler-cache": "^1.0.0-rc.151", "@tevm/client-types": "^1.0.0-rc.151", "@tevm/common": "^1.0.0-rc.151", "@tevm/compiler": "^1.0.0-rc.151", "@tevm/config": "^1.0.0-rc.151", "@tevm/consensus": "^1.0.0-rc.152", "@tevm/contract": "^1.0.0-rc.151", "@tevm/decorators": "^1.0.0-rc.151", "@tevm/errors": "^1.0.0-rc.151", "@tevm/esbuild-plugin": "^1.0.0-rc.151", "@tevm/evm": "^1.0.0-rc.151", "@tevm/http-client": "^1.0.0-rc.151", "@tevm/jsonrpc": "^1.0.0-rc.151", "@tevm/memory-client": "^1.0.0-rc.151", "@tevm/node": "^1.0.0-rc.151", "@tevm/precompiles": "^1.0.0-rc.151", "@tevm/predeploys": "^1.0.0-rc.151", "@tevm/receipt-manager": "^1.0.0-rc.151", "@tevm/rollup-plugin": "^1.0.0-rc.151", "@tevm/rspack-plugin": "^1.0.0-rc.151", "@tevm/server": "^1.0.0-rc.151", "@tevm/solc": "^1.0.0-rc.151", "@tevm/state": "^1.0.0-rc.151", "@tevm/sync-storage-persister": "^1.0.0-rc.151", "@tevm/ts-plugin": "^1.0.0-rc.151", "@tevm/tx": "^1.0.0-rc.151", "@tevm/txpool": "^1.0.0-rc.151", "@tevm/utils": "^1.0.0-rc.151", "@tevm/viem": "^1.0.0-rc.151", "@tevm/vite-plugin": "^1.0.0-rc.151", "@tevm/vm": "^1.0.0-rc.151", "@tevm/webpack-plugin": "^1.0.0-rc.151", "commander": "^14.0.3", "effect": "3.21.2", "glob": "^13.0.6", "solc": "^0.8.35" }, "peerDependencies": { "viem": "^2.49.3" }, "bin": { "tevm": "cli.js" } }, "sha512-LyFVsmOZUo4ZnFlZa4zgchK1Z2bHiXyihTlh3Ozin6IbgZa2ReJgdDz2I+UI+27IXzrRKGpMLy1WNAWbhaVSJA=="], @@ -858,11 +843,11 @@ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], - "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], - "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "tinyrainbow": ["tinyrainbow@3.1.1", "", {}, "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw=="], "tmp": ["tmp@0.2.7", "", {}, "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw=="], @@ -870,31 +855,31 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="], + "tsx": ["tsx@4.23.12", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q=="], "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], - "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="], + "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "viem": ["viem@2.53.1", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.29", "ws": "8.20.1" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-FhfJ/SW73CVosiyVLmIMVgKDRKYV1AGCLzZoHYvmNayyVff63Qi1ocPCk59LqC/cNw244RbBJjHnmxqXkE7NpA=="], + "viem": ["viem@2.55.19", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.34", "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-4QPIX0eYPLsOBk53NKswVMkQoxuP7GlOBnB4wM6dkDokREO4QENNc3bmyPKK1PBTViXh0TPJCHLjIuU20Qi3fg=="], - "vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="], + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], - "vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="], + "vitest": ["vitest@4.1.11", "", { "dependencies": { "@vitest/expect": "4.1.11", "@vitest/mocker": "4.1.11", "@vitest/pretty-format": "4.1.11", "@vitest/runner": "4.1.11", "@vitest/snapshot": "4.1.11", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.11", "@vitest/browser-preview": "4.1.11", "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-istanbul": "4.1.11", "@vitest/coverage-v8": "4.1.11", "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw=="], - "watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="], + "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], "web3-errors": ["web3-errors@1.3.1", "", { "dependencies": { "web3-types": "^1.10.0" } }, "sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ=="], @@ -906,9 +891,9 @@ "web3-validator": ["web3-validator@2.0.6", "", { "dependencies": { "ethereum-cryptography": "^2.0.0", "util": "^0.12.5", "web3-errors": "^1.2.0", "web3-types": "^1.6.0", "zod": "^3.21.4" } }, "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg=="], - "webpack": ["webpack@5.106.2", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "loader-runner": "^4.3.1", "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.17", "watchpack": "^2.5.1", "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA=="], + "webpack": ["webpack@5.109.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.24.4", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "mime-db": "^1.54.0", "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "watchpack": "^2.5.2", "webpack-sources": "^3.5.1" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw=="], - "webpack-sources": ["webpack-sources@3.4.1", "", {}, "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A=="], + "webpack-sources": ["webpack-sources@3.5.1", "", {}, "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -922,23 +907,19 @@ "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], - "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + "yoctocolors": ["yoctocolors@2.2.0", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="], "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@ethereumjs/binarytree/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.1", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-jbnWTEwcpoY+gE0r+wxfDG9zgiu54DcTcwnc9sX3DsqKR4l5K7x2V8mQL3Et6hURa4DuT9g7z6ukwpBLFchszg=="], + "@ethereumjs/binarytree/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], "@ethereumjs/binarytree/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], - "@ethereumjs/common/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], - - "@ethereumjs/evm/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], - - "@ethereumjs/mpt/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.1", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-jbnWTEwcpoY+gE0r+wxfDG9zgiu54DcTcwnc9sX3DsqKR4l5K7x2V8mQL3Et6hURa4DuT9g7z6ukwpBLFchszg=="], + "@ethereumjs/mpt/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], "@ethereumjs/mpt/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], - "@ethereumjs/statemanager/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.1", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-jbnWTEwcpoY+gE0r+wxfDG9zgiu54DcTcwnc9sX3DsqKR4l5K7x2V8mQL3Et6hURa4DuT9g7z6ukwpBLFchszg=="], + "@ethereumjs/statemanager/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], "@ethereumjs/statemanager/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], @@ -948,9 +929,9 @@ "@ethereumjs/trie/lru-cache": ["lru-cache@10.1.0", "", {}, "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag=="], - "@ethereumjs/tx/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.1", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-jbnWTEwcpoY+gE0r+wxfDG9zgiu54DcTcwnc9sX3DsqKR4l5K7x2V8mQL3Et6hURa4DuT9g7z6ukwpBLFchszg=="], + "@ethereumjs/tx/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], - "@ethereumjs/util/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.1", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-jbnWTEwcpoY+gE0r+wxfDG9zgiu54DcTcwnc9sX3DsqKR4l5K7x2V8mQL3Et6hURa4DuT9g7z6ukwpBLFchszg=="], + "@ethereumjs/util/@ethereumjs/rlp": ["@ethereumjs/rlp@10.1.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-T5Zt6C2pd02Wd88Q9A5/UX+He1Q2Y1LntHxz/038tfbUMiqby4fYSSTLEDx+TEfJqw1BsJSBY/TSu6goUzlk+w=="], "@latticexyz/common/abitype": ["abitype@1.0.9", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg=="], @@ -972,37 +953,35 @@ "@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], - "@tevm/base-bundler/@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + "@tevm/base-bundler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "@tevm/bundler-cache/@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + "@tevm/base-bundler/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], - "@tevm/compiler/@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + "@tevm/bun-plugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], - "@tevm/config/@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + "@tevm/bundler-cache/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "@tevm/resolutions/@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + "@tevm/compiler/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "@tevm/tsupconfig/@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + "@tevm/config/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "@types/readable-stream/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "@tevm/resolutions/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "@tevm/ts-plugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], - "es-set-tostringtag/hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "@tevm/tsupconfig/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "@tevm/unplugin/solc": ["solc@0.8.35", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-OaP/4zyoKRo2CjqZDxbtkeRlEo6MxP4FLCxntw1Agf9OSoecmwYKoFBSB34UcSKBFBucrTh3Mb0nRoJou62ibw=="], - "ethereum-cryptography/@noble/curves": ["@noble/curves@1.9.0", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg=="], + "@zoltar/ui-core-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], - "ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - - "form-data/hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "get-intrinsic/hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - "is-regex/hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "ethereum-cryptography/@noble/curves": ["@noble/curves@1.9.0", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg=="], - "jest-worker/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "ethereum-cryptography/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], @@ -1018,13 +997,13 @@ "prettier-plugin-solidity/@solidity-parser/parser": ["@solidity-parser/parser@0.17.0", "", {}, "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw=="], - "prettier-plugin-solidity/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "prettier-plugin-solidity/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "solc-typed-ast/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], - "solc-typed-ast/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "solc-typed-ast/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "solc-typed-ast/solc": ["solc@0.8.25", "", { "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", "semver": "^5.5.0", "tmp": "0.0.33" }, "bin": { "solcjs": "solc.js" } }, "sha512-7P0TF8gPeudl1Ko3RGkyY6XVCxe2SdD/qQhtns1vl3yAbK/PDifKDLHGtx1t7mX3LgR7ojV7Fg/Kc6Q9D2T8UQ=="], @@ -1040,8 +1019,6 @@ "viem/abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="], - "vite/tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], - "web3-eth-abi/abitype": ["abitype@0.7.1", "", { "peerDependencies": { "typescript": ">=4.9.4", "zod": "^3 >=3.19.1" }, "optionalPeers": ["zod"] }, "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ=="], "web3-utils/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], @@ -1060,6 +1037,10 @@ "@tevm/base-bundler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "@tevm/base-bundler/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "@tevm/bun-plugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "@tevm/bundler-cache/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], "@tevm/compiler/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], @@ -1068,11 +1049,11 @@ "@tevm/resolutions/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - "@tevm/tsupconfig/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "@tevm/ts-plugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - "@types/readable-stream/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "@tevm/tsupconfig/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - "jest-worker/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "@tevm/unplugin/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "solc-typed-ast/solc/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], diff --git a/ui/index.html b/ui/zoltar/index.html similarity index 96% rename from ui/index.html rename to ui/zoltar/index.html index 110e2a76d..2b70f6fa6 100644 --- a/ui/index.html +++ b/ui/zoltar/index.html @@ -3,9 +3,9 @@ - Zoltar + Augur Statoblast - - + Zoltar + + diff --git a/ui/zoltar/package.json b/ui/zoltar/package.json new file mode 100644 index 000000000..81f02312d --- /dev/null +++ b/ui/zoltar/package.json @@ -0,0 +1,52 @@ +{ + "description": "Zoltar oracle operations interface", + "name": "@zoltar/ui-zoltar", + "private": true, + "repository": {}, + "license": "Unlicense", + "type": "module", + "devDependencies": { + "better-typescript-lib": "2.11.0", + "bun-types": "1.3.11", + "typescript": "5.8.2", + "funtypes": "5.1.1" + }, + "dependencies": { + "@preact/signals": "2.0.1", + "@noble/curves": "2.2.0", + "@noble/hashes": "2.2.0", + "@zoltar/shared": "file:../../shared", + "@zoltar/ui-core-shared": "file:../coreShared", + "buffer": "6.0.3", + "micro-eth-signer": "0.18.1", + "micro-packed": "0.8.0", + "preact": "10.26.4", + "source-map": "0.6.1", + "tevm": "1.0.0-rc.151" + }, + "overrides": { + "@tevm/actions": "1.0.0-rc.151", + "@tevm/node": "1.0.0-rc.151", + "@tevm/server": "1.0.0-rc.151", + "brace-expansion": "5.0.9", + "fast-uri": "3.1.5", + "nanoid": "3.3.18", + "postcss": "8.5.23", + "tmp": "0.2.7", + "ws": "8.21.0" + }, + "scripts": { + "generate": "bun ../coreShared/build/projectArtifacts.mts && bun ../coreShared/build/vendor.mts zoltar", + "vendor": "bun ../coreShared/build/vendor.mts zoltar", + "build:workers": "bun ../coreShared/build/workers.mts zoltar", + "build:tests": "bun ../coreShared/build/tests.mts zoltar", + "build": "bun run generate && bun x tsc --project tsconfig.json && bun run build:workers", + "build:prod": "bun run generate && bun ../coreShared/build/production.mts zoltar", + "watch": "bun ../coreShared/build/watch.mts zoltar", + "serve": "bun ../coreShared/dev-server.ts zoltar" + }, + "types": "./js/index.d.ts", + "exports": { + "./*": "./js/*.js" + } +} diff --git a/ui/zoltar/ts/app/App.tsx b/ui/zoltar/ts/app/App.tsx new file mode 100644 index 000000000..ed03c72bd --- /dev/null +++ b/ui/zoltar/ts/app/App.tsx @@ -0,0 +1,491 @@ +import * as appCopy from '@zoltar/ui-core-shared/copy/app.js' +import * as marketCopy from '../copy/market.js' +import * as zoltarCopy from '../copy/zoltar.js' +import { useSignal } from '@preact/signals' +import type { ComponentChildren } from 'preact' +import { useRef, useState } from 'preact/hooks' +import type { Hash } from '@zoltar/shared/ethereum' +import { AppHeaderShell } from '@zoltar/ui-core-shared/app/components/AppHeaderShell.js' +import { AppPageHeading } from '@zoltar/ui-core-shared/app/components/AppPageHeading.js' +import { AppStatusNotices } from '@zoltar/ui-core-shared/app/components/AppStatusNotices.js' +import { GlobalTransactionTray } from '@zoltar/ui-core-shared/app/components/GlobalTransactionTray.js' +import { GlobalTransactionPresentationProvider } from '@zoltar/ui-core-shared/components/GlobalTransactionPresentationContext.js' +import { TransactionActionButtonLockProvider } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { RouteSubNavigation } from '@zoltar/ui-core-shared/app/components/RouteSubNavigation.js' +import { AppRouteContent } from './components/AppRouteContent.js' +import { OverviewPanels } from './components/OverviewPanels.js' +import { useAppRouteEffects } from './hooks/useAppRouteEffects.js' +import { useDeploymentFlow } from '../features/deployment/hooks/useDeploymentFlow.js' +import { useHashRoute } from '@zoltar/ui-core-shared/app/hooks/useHashRoute.js' +import { useOnchainState } from '@zoltar/ui-core-shared/app/hooks/useOnchainState.js' +import { useOpenOracleOperations } from '../features/open-oracle/hooks/useOpenOracleOperations.js' +import { useZoltarOperations } from '../features/universes/hooks/useZoltarOperations.js' +import { useRepPrices } from '../features/open-oracle/hooks/useRepPrices.js' +import { useUrlState } from '@zoltar/ui-core-shared/app/hooks/useUrlState.js' +import { getActiveSimulationController, initializeActiveEnvironment, shouldFollowWalletNetwork } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { formatAppDocumentTitle, getAppPageTitle } from './lib/appPageTitle.js' +import { createSupportedNetworkChangeCoordinator } from '@zoltar/ui-core-shared/app/lib/supportedNetworkChange.js' +import { ChainBlockNumberContext, ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { onchainStateDependencies } from './onchainStateDependencies.js' +import { getDeploymentSections } from '../features/deployment/lib/deployment.js' +import { resolveLoadableValueState } from '@zoltar/ui-core-shared/lib/loadState.js' +import { getWalletScopedAccountAddress, isSupportedAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { createInitialTransactionTrayState, getTransactionActionLockReason, markTransactionCanceled, markTransactionFailed, markTransactionFinished, markTransactionPrepared, markTransactionPresented, markTransactionRequested, markTransactionSubmitted } from '@zoltar/ui-core-shared/lib/transactionTray.js' +import type { TransactionTrayState } from '@zoltar/ui-core-shared/lib/transactionTray.js' +import type { TransactionRequestPreview } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import { buildRouteHref, getRouteHash, getRouteHashSearch } from '@zoltar/ui-core-shared/lib/routing.js' +import { writeOpenOracleViewQueryParam, writeZoltarViewQueryParam } from '@zoltar/ui-core-shared/lib/urlParams.js' +import { getUniversePresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import { formatUniverseCollectionLabel } from '../features/universes/lib/universe.js' +import { resolveEnumValue, resolveFirstMatchingValue } from '@zoltar/ui-core-shared/lib/viewState.js' +import type { RouteTabDefinition } from '@zoltar/ui-core-shared/types/components.js' +import type { DeploymentRouteContentProps, MarketRouteContentProps, OpenOracleSectionProps, OpenOracleView, ZoltarView } from '../features/types.js' +import type { Route } from '../types/app.js' +import type { GlobalTransactionPresentation, TransactionIntent } from '@zoltar/ui-core-shared/types/components.js' + +export function App() { + const transactionState = useSignal(createInitialTransactionTrayState()) + const deployNextMissingPending = useSignal(false) + const [activeEnvironmentNonce, setActiveEnvironmentNonce] = useState(0) + const followSupportedWalletNetwork = shouldFollowWalletNetwork() + const supportedNetworkChangeCoordinatorRef = useRef>() + const supportedNetworkChangeCoordinator = + supportedNetworkChangeCoordinatorRef.current ?? + createSupportedNetworkChangeCoordinator({ + getInFlightCount: () => transactionState.value.inFlightCount, + replaceEnvironment: async canCommit => { + let commitAllowed = false + await initializeActiveEnvironment(window.location, undefined, { + shouldCommit: () => { + commitAllowed = canCommit() + return commitAllowed + }, + }) + if (!commitAllowed) return false + setActiveEnvironmentNonce(currentNonce => currentNonce + 1) + return true + }, + }) + supportedNetworkChangeCoordinatorRef.current = supportedNetworkChangeCoordinator + const { activeUniverseId, openOracleReportId: urlOpenOracleReportId, openOracleView, setActiveUniverseId, setOpenOracleReport, setOpenOracleView, setZoltarView, zoltarView } = useUrlState() + const activeZoltarView = resolveEnumValue(zoltarView, 'questions', ['questions', 'create', 'fork', 'migrate']) + const onTransactionRequested = (intent: TransactionIntent) => { + transactionState.value = markTransactionRequested(transactionState.value, intent) + } + const onTransactionPrepared = (preview: TransactionRequestPreview) => { + transactionState.value = markTransactionPrepared(transactionState.value, preview) + } + const onTransactionSubmitted = (hash: Hash) => { + transactionState.value = markTransactionSubmitted(transactionState.value, hash) + } + const onTransactionFailed = (message: string) => { + transactionState.value = markTransactionFailed(transactionState.value, message) + } + const onTransactionCanceled = () => { + transactionState.value = markTransactionCanceled(transactionState.value) + } + const onTransactionPresented = (presentation: GlobalTransactionPresentation) => { + transactionState.value = markTransactionPresented(transactionState.value, presentation) + } + const onTransactionFinished = () => { + transactionState.value = markTransactionFinished(transactionState.value) + void supportedNetworkChangeCoordinator.handleTransactionFinished() + } + const { navigate, route } = useHashRoute() + const activeRoute = resolveEnumValue(route, 'not-found', ['deploy', 'zoltar', 'open-oracle', 'not-found']) + const { + accountState, + applicationDeploymentComplete, + changeWallet, + chainClockError, + connectWallet, + currentBlockNumber, + currentTimestamp, + deploymentStatusError, + deploymentStatuses, + environmentBootstrapError, + environmentReady, + errorMessages: onchainErrorMessages, + readBackendMessage, + readBackendValidated, + readBackendStatus, + hasLoadedDeploymentStatuses, + isConnectingWallet, + isManagingWallet, + isLoadingDeploymentStatuses, + isRefreshing, + refreshState, + setDeploymentStatuses, + disconnectWallet, + switchNetwork, + walletBootstrapComplete, + } = useOnchainState( + { + activeEnvironmentNonce, + enableChainClock: route !== 'deploy', + ...(followSupportedWalletNetwork ? { onSupportedNetworkChange: () => void supportedNetworkChangeCoordinator.handleSupportedNetworkChange() } : {}), + }, + onchainStateDependencies, + ) + const readBackendReady = readBackendValidated && readBackendMessage === undefined + const canReadOnchainData = environmentReady && readBackendReady && hasLoadedDeploymentStatuses + const isOnActiveAppChain = isSupportedAppChain(accountState.chainId) + const walletScopedAccountAddress = getWalletScopedAccountAddress(accountState.address, accountState.chainId) + const baseHookConfig = { + accountAddress: accountState.address, + onTransactionCanceled, + onTransactionFailed, + onTransactionFinished, + onTransactionPresented, + onTransactionPrepared, + onTransactionRequested, + onTransactionSubmitted, + refreshState, + } + const walletScopedHookConfig = { + ...baseHookConfig, + accountAddress: walletScopedAccountAddress, + } + const { busyStepId, deployNextMissing, deployStep, errorMessage: deploymentErrorMessage } = useDeploymentFlow({ ...baseHookConfig, deploymentStatuses, setDeploymentStatuses }) + const { + approveZoltarForkRep, + createChildUniverse, + forkZoltar, + hasLoadedZoltarQuestions, + loadingZoltarForkAccess, + loadingZoltarQuestionCount, + loadingZoltarQuestion, + loadingZoltarQuestions, + loadingZoltarUniverse, + loadZoltarQuestionPage, + loadZoltarQuestion, + loadZoltarQuestions, + loadZoltarUniverse, + migrateInternalRep, + prepareRepForMigration, + setZoltarForkQuestionId, + setZoltarMigrationForm, + zoltarChildUniverseError, + zoltarChildUniversePendingOutcomeIndex, + zoltarForkApproval, + zoltarForkActiveAction, + zoltarForkError, + zoltarForkPending, + zoltarForkQuestionId, + zoltarForkRepBalanceAttoRep, + zoltarMigrationChildRepBalancesAttoRep, + zoltarMigrationActiveAction, + zoltarMigrationError, + zoltarMigrationForm, + zoltarMigrationPending, + zoltarMigrationPreparedRepBalanceAttoRep, + zoltarQuestionCount, + zoltarQuestionLookupError, + zoltarQuestionLookupId, + zoltarQuestionPage, + zoltarQuestions, + zoltarQuestionsError, + zoltarUniverse, + zoltarUniverseError, + zoltarUniverseMissing, + } = useZoltarOperations({ ...walletScopedHookConfig, activeUniverseId, activeZoltarView, autoLoadInitialData: walletBootstrapComplete && canReadOnchainData, deploymentStatuses, environmentRefreshKey: activeEnvironmentNonce }) + const { + approveToken1, + approveToken2, + cancelWithdrawalBalanceCheck, + createOpenOracleGame, + disputeReport, + loadOracleReport, + loadingOpenOracleCreate, + openOracleActiveAction, + openOracleActiveWithdrawalBalance, + openOracleCreateForm, + openOracleCreateFieldErrors, + openOracleDisputeSubmission, + openOracleError, + openOracleForm, + openOracleReportLookupState, + openOracleTokenAccessState, + openOracleReportDetails, + openOracleResult, + openOracleWithdrawalBalanceChecking, + openOracleWithdrawalReviewMessage, + openOracleWithdrawableBalances, + openOracleWithdrawableBalancesError, + openOracleWithdrawableBalancesLoading, + setOpenOracleCreateForm, + setOpenOracleForm, + settleReport, + withdrawBalance, + } = useOpenOracleOperations({ + ...walletScopedHookConfig, + enabled: route === 'open-oracle' && canReadOnchainData, + }) + const { repPerEthFailure, repPerEthPrice, repPerEthSource, repPerEthSourceUrl, repUsdcFailure, repUsdcPrice, repUsdcSource, repUsdcSourceUrl, isLoadingRepPrices, isRefreshingRepPrices, refreshRepPrices } = useRepPrices() + const simulationController = getActiveSimulationController() + const refreshSimulationView = async () => { + await refreshState() + refreshRepPrices() + } + const refreshActiveEnvironment = async () => { + await initializeActiveEnvironment() + setActiveEnvironmentNonce(currentNonce => currentNonce + 1) + await refreshSimulationView() + } + const deploymentSections = getDeploymentSections(deploymentStatuses) + const errorMessages = [deploymentErrorMessage, ...onchainErrorMessages.filter(message => message !== deploymentStatusError), chainClockError].filter((message): message is string => message !== undefined) + const applicationDeploymentMissing = canReadOnchainData && applicationDeploymentComplete === false + const showDeployTab = deploymentStatusError !== undefined || applicationDeploymentMissing || (hasLoadedDeploymentStatuses && deploymentStatuses.some(step => !step.deployed)) + const showApplicationDeploymentWarning = applicationDeploymentMissing + const zoltarUniverseState = resolveLoadableValueState({ + isLoading: loadingZoltarUniverse, + isMissing: zoltarUniverseMissing, + value: zoltarUniverse, + }) + const showZoltarUniverseWarning = canReadOnchainData && zoltarUniverseState === 'missing' + const isRouteContentDisabled = route !== 'deploy' && (!readBackendReady || applicationDeploymentMissing || showZoltarUniverseWarning) + const universeLabel = formatUniverseCollectionLabel([activeUniverseId]) + const universePresentation = showZoltarUniverseWarning ? getUniversePresentation(zoltarUniverseState) : undefined + const derivedOpenOracleView = resolveFirstMatchingValue([[urlOpenOracleReportId !== '' || openOracleForm.reportId !== '', 'selected-report']], 'browse') + const activeOpenOracleView = resolveEnumValue(openOracleView, derivedOpenOracleView, ['browse', 'create', 'selected-report']) + const pageTitle = getAppPageTitle({ activeOpenOracleView, activeZoltarView, route: activeRoute }) + useAppRouteEffects({ + applicationDeploymentMissing, + environmentReady: canReadOnchainData, + activeEnvironmentNonce, + loadOracleReport: async reportId => await loadOracleReport(reportId), + navigate, + route: activeRoute, + setOpenOracleFormReportId: reportId => setOpenOracleForm(current => ({ ...current, reportId })), + urlOpenOracleReportId, + }) + const onDeployNextMissing = async () => { + if (deployNextMissingPending.value) return + deployNextMissingPending.value = true + try { + await deployNextMissing() + } finally { + deployNextMissingPending.value = false + } + } + const deployRouteContentProps: DeploymentRouteContentProps = { + accountAddress: accountState.address, + busyStepId, + deploymentStateReady: hasLoadedDeploymentStatuses && environmentReady && readBackendReady, + deploymentStatusError, + deployNextMissingPending: deployNextMissingPending.value, + deploymentSections, + deploymentStatuses, + isLoadingDeploymentStatuses, + isOnActiveAppChain, + onDeploy: deployStep, + onDeployNextMissing: () => void onDeployNextMissing(), + onRetryDeploymentStatus: () => void refreshState({ loadChainClock: false, loadWalletState: false }), + } + const zoltarRouteContentProps: MarketRouteContentProps = { + accountState, + activeUniverseId, + activeView: activeZoltarView, + environmentRefreshKey: activeEnvironmentNonce, + hasLoadedZoltarQuestions, + loadingZoltarForkAccess, + loadingZoltarQuestion, + loadingZoltarQuestionCount, + loadingZoltarQuestions, + loadingZoltarUniverse, + onActiveViewChange: view => setZoltarView(view), + onApproveZoltarForkRep: amount => void approveZoltarForkRep(amount), + onCreateChildUniverseForOutcomeIndex: outcomeIndex => void createChildUniverse(outcomeIndex), + onForkZoltar: () => void forkZoltar(), + onLoadZoltarQuestion: async questionId => await loadZoltarQuestion(questionId), + onLoadZoltarQuestionPage: async (pageIndex, pageSize) => await loadZoltarQuestionPage(pageIndex, pageSize), + onLoadZoltarQuestions: async () => await loadZoltarQuestions(), + onMigrateInternalRep: () => void migrateInternalRep(), + onPrepareRepForMigration: () => void prepareRepForMigration(), + onZoltarForkQuestionIdChange: questionId => setZoltarForkQuestionId(questionId), + onZoltarMigrationFormChange: update => setZoltarMigrationForm(current => ({ ...current, ...update })), + zoltarChildUniverseError, + zoltarChildUniversePendingOutcomeIndex, + zoltarForkActiveAction, + zoltarForkApproval, + zoltarForkError, + zoltarForkPending, + zoltarForkQuestionId, + zoltarForkRepBalanceAttoRep, + zoltarMigrationActiveAction, + zoltarMigrationChildRepBalancesAttoRep, + zoltarMigrationError, + zoltarMigrationForm, + zoltarMigrationPending, + zoltarMigrationPreparedRepBalanceAttoRep, + zoltarQuestionCount, + zoltarQuestionLookupError, + zoltarQuestionLookupId, + zoltarQuestionPage, + zoltarQuestions, + zoltarQuestionsError, + zoltarUniverse, + zoltarUniverseState, + } + const openOracleRouteContentProps: OpenOracleSectionProps = { + activeView: activeOpenOracleView, + accountState, + environmentReady: canReadOnchainData, + environmentRefreshKey: activeEnvironmentNonce, + onApproveToken1: amount => void approveToken1(amount), + onApproveToken2: amount => void approveToken2(amount), + onCancelOpenOracleWithdrawalBalanceCheck: cancelWithdrawalBalanceCheck, + onCreateOpenOracleGame: () => void createOpenOracleGame(), + onDisputeReport: () => void disputeReport(), + onLoadOracleReport: reportId => { + if (reportId === undefined) return + void loadOracleReport(reportId) + }, + onActiveViewChange: view => setOpenOracleView(view), + onOpenOracleCreateFormChange: update => setOpenOracleCreateForm(current => ({ ...current, ...update })), + onOpenOracleFormChange: update => { + setOpenOracleForm(current => ({ ...current, ...update })) + if (update.reportId !== undefined) setOpenOracleReport(update.reportId) + }, + onSettleReport: () => void settleReport(), + onWithdrawOpenOracleBalance: (balance, reviewedAmount) => void withdrawBalance(balance, reviewedAmount), + loadingOpenOracleCreate, + openOracleActiveAction, + openOracleActiveWithdrawalBalance, + openOracleError, + openOracleCreateForm, + openOracleCreateFieldErrors, + openOracleDisputeSubmission, + openOracleForm, + openOracleReportLookupState, + openOracleTokenAccessState, + openOracleReportDetails, + openOracleResult, + openOracleWithdrawalBalanceChecking, + openOracleWithdrawalReviewMessage, + openOracleWithdrawableBalances, + openOracleWithdrawableBalancesError, + openOracleWithdrawableBalancesLoading, + } + const tabNavigationTabs: RouteTabDefinition[] = [ + ...(showDeployTab ? [{ hash: getRouteHash('deploy'), label: appCopy.deployContracts, route: 'deploy' }] : []), + { hash: getRouteHash('zoltar'), label: marketCopy.questions, route: 'zoltar' }, + { hash: getRouteHash('open-oracle'), label: appCopy.oracleReports, route: 'open-oracle' }, + ] + const tabNavigationProps = { + route, + tabs: tabNavigationTabs, + onRouteChange: navigate, + } + let routeSubNavigation: ComponentChildren = undefined + if (route === 'zoltar') { + routeSubNavigation = ( + setZoltarView(view)} + options={[ + { href: buildRouteHref(getRouteHash('zoltar'), writeZoltarViewQueryParam(getRouteHashSearch(), 'questions')), label: marketCopy.browseQuestions, value: 'questions' }, + { href: buildRouteHref(getRouteHash('zoltar'), writeZoltarViewQueryParam(getRouteHashSearch(), 'fork')), label: marketCopy.forkUniverse, value: 'fork' }, + { + label: marketCopy.repMigration, + value: 'migrate', + disabled: zoltarUniverse?.hasForked !== true, + ...(zoltarUniverse?.hasForked === true ? { href: buildRouteHref(getRouteHash('zoltar'), writeZoltarViewQueryParam(getRouteHashSearch(), 'migrate')) } : { reason: zoltarCopy.migrationNotForkedReason }), + }, + ]} + /> + ) + } else if (route === 'open-oracle') { + routeSubNavigation = ( + setOpenOracleView(view)} + options={[ + { href: buildRouteHref(getRouteHash('open-oracle'), writeOpenOracleViewQueryParam(getRouteHashSearch(), 'browse')), label: appCopy.browseReports, value: 'browse' }, + { href: buildRouteHref(getRouteHash('open-oracle'), writeOpenOracleViewQueryParam(getRouteHashSearch(), 'create')), label: appCopy.createReport, value: 'create' }, + { href: buildRouteHref(getRouteHash('open-oracle'), writeOpenOracleViewQueryParam(getRouteHashSearch(), 'selected-report')), label: appCopy.viewReport, value: 'selected-report' }, + ]} + /> + ) + } + const transactionRouteKey = (() => { + if (route === 'zoltar') return `${route}:${activeZoltarView}` + if (route === 'open-oracle') return `${route}:${activeOpenOracleView}` + return route + })() + + return ( + + +
+ + void loadZoltarUniverse({ clearCurrentState: false })} + readBackendMessage={readBackendMessage} + readBackendStatus={readBackendStatus} + simulationBootstrapError={environmentBootstrapError} + showApplicationDeploymentWarning={showApplicationDeploymentWarning} + zoltarUniverseError={zoltarUniverseError} + /> + void connectWallet()} + onChangeWallet={() => void changeWallet()} + onDisconnectWallet={() => void disconnectWallet()} + onGoToGenesisUniverse={() => setActiveUniverseId(0n)} + onRefreshRepPrices={refreshRepPrices} + onSwitchNetwork={() => void switchNetwork()} + parentUniverseId={zoltarUniverse?.parentUniverseId} + repPerEthFailure={repPerEthFailure} + repPerEthPrice={repPerEthPrice} + repPerEthSource={repPerEthSource} + repPerEthSourceUrl={repPerEthSourceUrl} + repUsdcFailure={repUsdcFailure} + repUsdcPrice={repUsdcPrice} + repUsdcSource={repUsdcSource} + repUsdcSourceUrl={repUsdcSourceUrl} + readBackendStatus={readBackendStatus} + universeForkTime={zoltarUniverse?.forkTime} + universeHasForked={zoltarUniverse?.hasForked} + universePresentation={universePresentation} + universeLabel={universeLabel} + universeRepBalanceAttoRep={zoltarForkRepBalanceAttoRep} + isRefreshing={isRefreshing} + walletBootstrapComplete={walletBootstrapComplete} + /> + } + simulationController={simulationController} + subNavigation={routeSubNavigation} + tabNavigation={tabNavigationProps} + onEnvironmentChanged={refreshActiveEnvironment} + onRefresh={refreshSimulationView} + /> + + + +
+ +
+ +
+
+
+
+
+
+
+ ) +} diff --git a/ui/ts/app/components/AppRouteContent.tsx b/ui/zoltar/ts/app/components/AppRouteContent.tsx similarity index 54% rename from ui/ts/app/components/AppRouteContent.tsx rename to ui/zoltar/ts/app/components/AppRouteContent.tsx index daf1e91aa..69c26966d 100644 --- a/ui/ts/app/components/AppRouteContent.tsx +++ b/ui/zoltar/ts/app/components/AppRouteContent.tsx @@ -1,36 +1,32 @@ import type { ComponentProps } from 'preact' import { DeploymentRouteContent } from '../../features/deployment/components/DeploymentRouteContent.js' -import { MarketSection } from '../../features/markets/components/MarketSection.js' -import { NotFoundSection } from './NotFoundSection.js' +import { NotFoundSection } from '@zoltar/ui-core-shared/app/components/NotFoundSection.js' import { OpenOracleSection } from '../../features/open-oracle/components/OpenOracleSection.js' -import { SecurityPoolsSection } from '../../features/security-pools/components/SecurityPoolsSection.js' +import { ZoltarSection } from '../../features/zoltarSurface/components/ZoltarSection.js' +import { shouldRenderAppRouteContent } from '@zoltar/ui-core-shared/app/lib/appRouteGate.js' -type AppRoute = 'deploy' | 'not-found' | 'open-oracle' | 'security-pools' | 'zoltar' +type AppRoute = 'deploy' | 'not-found' | 'open-oracle' | 'zoltar' type Props = { deploy: ComponentProps - market: ComponentProps + zoltar: ComponentProps openOracle: ComponentProps readBackendMessage: string | undefined route: AppRoute - securityPools: ComponentProps } export function shouldRenderRouteContent({ readBackendMessage, route }: Pick) { - if (route !== 'deploy' && readBackendMessage !== undefined) return false - return true + return shouldRenderAppRouteContent(route, readBackendMessage) } -export function AppRouteContent({ deploy, market, openOracle, readBackendMessage, route, securityPools }: Props) { +export function AppRouteContent({ deploy, zoltar, openOracle, readBackendMessage, route }: Props) { if (!shouldRenderRouteContent({ readBackendMessage, route })) return null switch (route) { case 'deploy': return case 'zoltar': - return - case 'security-pools': - return + return case 'open-oracle': return case 'not-found': diff --git a/ui/ts/app/components/OverviewPanels.tsx b/ui/zoltar/ts/app/components/OverviewPanels.tsx similarity index 89% rename from ui/ts/app/components/OverviewPanels.tsx rename to ui/zoltar/ts/app/components/OverviewPanels.tsx index c41ffd1c9..975be055f 100644 --- a/ui/ts/app/components/OverviewPanels.tsx +++ b/ui/zoltar/ts/app/components/OverviewPanels.tsx @@ -1,20 +1,20 @@ -import * as appCopy from '../../copy/app.js' -import * as commonCopy from '../../copy/common.js' +import * as appCopy from '@zoltar/ui-core-shared/copy/app.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import { useState } from 'preact/hooks' -import { RouteHeader } from '../../components/RouteHeader.js' -import { AddressValue } from '../../components/AddressValue.js' -import { Badge } from '../../components/Badge.js' -import { CurrencyValue } from '../../components/CurrencyValue.js' -import { DataGrid } from '../../components/DataGrid.js' -import { MetricField } from '../../components/MetricField.js' -import { LoadingText } from '../../components/LoadingText.js' -import { StateHint } from '../../components/StateHint.js' -import { TimestampValue } from '../../components/TimestampValue.js' -import { getChainDisplayLabel, getChainIdDecimalLabel, getKnownChainName, isActiveAppChain } from '../../lib/network.js' +import { RouteHeader } from '@zoltar/ui-core-shared/components/RouteHeader.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' +import { getChainDisplayLabel, getChainIdDecimalLabel, getKnownChainName, isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' import { renderRepPriceSourceLabel } from '../../features/open-oracle/lib/repPriceSource.js' import type { OverviewPanelsProps, RepPriceFailure } from '../../features/types.js' -import { getActiveNetworkProfile } from '../../lib/activeEnvironment.js' -import { getNetworkSwitchTarget } from '../../lib/networkProfile.js' +import { getActiveNetworkProfile } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { getNetworkSwitchTarget } from '@zoltar/ui-core-shared/lib/networkProfile.js' function getWalletNetworkLabel(chainId: string | undefined) { if (chainId === undefined) return appCopy.unknownNetwork @@ -37,6 +37,7 @@ function renderRepPriceFailure(failure: RepPriceFailure | undefined) { } export function OverviewPanels({ + applicationTitle, accountState, isConnectingWallet, isManagingWallet, @@ -162,7 +163,7 @@ export function OverviewPanels({ } description={operationsHeaderDescription} eyebrow={appCopy.operations} - title={appCopy.augurStatoblastTitle} + title={applicationTitle} /> diff --git a/ui/zoltar/ts/app/hooks/useAppRouteEffects.ts b/ui/zoltar/ts/app/hooks/useAppRouteEffects.ts new file mode 100644 index 000000000..81b6eb912 --- /dev/null +++ b/ui/zoltar/ts/app/hooks/useAppRouteEffects.ts @@ -0,0 +1,57 @@ +import { useEffect, useRef } from 'preact/hooks' + +type AppRoute = 'deploy' | 'not-found' | 'open-oracle' | 'zoltar' + +type Props = { + applicationDeploymentMissing: boolean + activeEnvironmentNonce: number + environmentReady: boolean + loadOracleReport: (reportId: string) => Promise + navigate: (route: 'deploy' | 'open-oracle' | 'zoltar') => void + route: AppRoute + setOpenOracleFormReportId: (reportId: string) => void + urlOpenOracleReportId: string +} + +export function shouldLoadOpenOracleReportFromUrl({ environmentReady, route, urlOpenOracleReportId }: { environmentReady: boolean; route: AppRoute; urlOpenOracleReportId: string }) { + return environmentReady && route === 'open-oracle' && urlOpenOracleReportId !== '' +} + +export function useAppRouteEffects({ applicationDeploymentMissing, activeEnvironmentNonce, environmentReady, loadOracleReport, navigate, route, setOpenOracleFormReportId, urlOpenOracleReportId }: Props) { + const loadOracleReportRef = useRef(loadOracleReport) + const navigateRef = useRef(navigate) + const lastRequestedOpenOracleReportId = useRef(undefined) + const lastSyncedOpenOracleReportId = useRef(undefined) + + loadOracleReportRef.current = loadOracleReport + navigateRef.current = navigate + + useEffect(() => { + if (route !== 'open-oracle') { + lastSyncedOpenOracleReportId.current = undefined + return + } + const normalizedReportId = urlOpenOracleReportId.trim() + if (lastSyncedOpenOracleReportId.current === normalizedReportId) return + lastSyncedOpenOracleReportId.current = normalizedReportId + setOpenOracleFormReportId(normalizedReportId) + }, [route, setOpenOracleFormReportId, urlOpenOracleReportId]) + + useEffect(() => { + const shouldLoadReport = shouldLoadOpenOracleReportFromUrl({ environmentReady, route, urlOpenOracleReportId }) + if (!shouldLoadReport) { + lastRequestedOpenOracleReportId.current = undefined + return + } + const requestKey = `${activeEnvironmentNonce}:${urlOpenOracleReportId}` + if (lastRequestedOpenOracleReportId.current === requestKey) return + lastRequestedOpenOracleReportId.current = requestKey + void loadOracleReportRef.current(urlOpenOracleReportId) + }, [activeEnvironmentNonce, environmentReady, route, urlOpenOracleReportId]) + + useEffect(() => { + if (!applicationDeploymentMissing) return + if (route === 'deploy') return + navigateRef.current('deploy') + }, [applicationDeploymentMissing, route]) +} diff --git a/ui/ts/app/lib/appPageTitle.ts b/ui/zoltar/ts/app/lib/appPageTitle.ts similarity index 58% rename from ui/ts/app/lib/appPageTitle.ts rename to ui/zoltar/ts/app/lib/appPageTitle.ts index 6959f8eae..c38a6a691 100644 --- a/ui/ts/app/lib/appPageTitle.ts +++ b/ui/zoltar/ts/app/lib/appPageTitle.ts @@ -1,18 +1,17 @@ -import * as appCopy from '../../copy/app.js' -import * as commonCopy from '../../copy/common.js' +import * as appCopy from '@zoltar/ui-core-shared/copy/app.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as marketCopy from '../../copy/market.js' import * as zoltarCopy from '../../copy/zoltar.js' import type { Route } from '../../types/app.js' -import type { OpenOracleView, SecurityPoolsView, ZoltarView } from '../../features/types.js' +import type { OpenOracleView, ZoltarView } from '../../features/types.js' export type AppPageTitleInput = { activeOpenOracleView: OpenOracleView - activeSecurityPoolsView: SecurityPoolsView activeZoltarView: ZoltarView route: Route } -export function getAppPageTitle({ activeOpenOracleView, activeSecurityPoolsView, activeZoltarView, route }: AppPageTitleInput) { +export function getAppPageTitle({ activeOpenOracleView, activeZoltarView, route }: AppPageTitleInput) { if (route === 'deploy') return appCopy.deployContracts if (route === 'zoltar') { if (activeZoltarView === 'create') return commonCopy.createQuestion @@ -20,11 +19,6 @@ export function getAppPageTitle({ activeOpenOracleView, activeSecurityPoolsView, if (activeZoltarView === 'migrate') return zoltarCopy.migrateRep return marketCopy.questions } - if (route === 'security-pools') { - if (activeSecurityPoolsView === 'create') return commonCopy.createSecurityPool - if (activeSecurityPoolsView === 'operate') return appCopy.manageSecurityPool - return commonCopy.securityPools - } if (route === 'open-oracle') { if (activeOpenOracleView === 'create') return appCopy.createOracleReport if (activeOpenOracleView === 'selected-report') return appCopy.oracleReportDetails @@ -34,5 +28,5 @@ export function getAppPageTitle({ activeOpenOracleView, activeSecurityPoolsView, } export function formatAppDocumentTitle(pageTitle: string) { - return `${pageTitle} | ${appCopy.appDocumentTitleSuffix}` + return `${pageTitle} | ${zoltarCopy.applicationTitle}` } diff --git a/ui/zoltar/ts/app/onchainStateDependencies.ts b/ui/zoltar/ts/app/onchainStateDependencies.ts new file mode 100644 index 000000000..65119295a --- /dev/null +++ b/ui/zoltar/ts/app/onchainStateDependencies.ts @@ -0,0 +1,4 @@ +import { getDeploymentSteps, loadDeploymentStatusOracleSnapshot, loadErc20Balance } from '../protocol/index.js' +import { getWethAddress } from '../protocol/uniswapQuoter.js' + +export const onchainStateDependencies = { getDeploymentSteps, getWethAddress, loadDeploymentStatusOracleSnapshot, loadErc20Balance } diff --git a/ui/ts/copy/deployment.ts b/ui/zoltar/ts/copy/deployment.ts similarity index 100% rename from ui/ts/copy/deployment.ts rename to ui/zoltar/ts/copy/deployment.ts diff --git a/ui/ts/copy/forkAuction.ts b/ui/zoltar/ts/copy/forkAuction.ts similarity index 99% rename from ui/ts/copy/forkAuction.ts rename to ui/zoltar/ts/copy/forkAuction.ts index d60913717..e4e870c88 100644 --- a/ui/ts/copy/forkAuction.ts +++ b/ui/zoltar/ts/copy/forkAuction.ts @@ -1,4 +1,4 @@ -import type { CopyTemplateValue } from './types.js' +import type { CopyTemplateValue } from '@zoltar/ui-core-shared/copy/types.js' export const bidAmountEth = 'Bid Amount (ETH)' export const scrollableAuctionBidHistory = 'Scrollable auction bid history' diff --git a/ui/ts/copy/liquidation.ts b/ui/zoltar/ts/copy/liquidation.ts similarity index 100% rename from ui/ts/copy/liquidation.ts rename to ui/zoltar/ts/copy/liquidation.ts diff --git a/ui/ts/copy/market.ts b/ui/zoltar/ts/copy/market.ts similarity index 100% rename from ui/ts/copy/market.ts rename to ui/zoltar/ts/copy/market.ts diff --git a/ui/ts/copy/openOracle.ts b/ui/zoltar/ts/copy/openOracle.ts similarity index 99% rename from ui/ts/copy/openOracle.ts rename to ui/zoltar/ts/copy/openOracle.ts index 531547abe..26e801d6c 100644 --- a/ui/ts/copy/openOracle.ts +++ b/ui/zoltar/ts/copy/openOracle.ts @@ -1,4 +1,4 @@ -import type { CopyTemplateValue } from './types.js' +import type { CopyTemplateValue } from '@zoltar/ui-core-shared/copy/types.js' export const settleReport = 'Settle Report' export const settleReportAction = 'Settle report' diff --git a/ui/ts/copy/reporting.ts b/ui/zoltar/ts/copy/reporting.ts similarity index 100% rename from ui/ts/copy/reporting.ts rename to ui/zoltar/ts/copy/reporting.ts diff --git a/ui/ts/copy/securityPool.ts b/ui/zoltar/ts/copy/securityPool.ts similarity index 99% rename from ui/ts/copy/securityPool.ts rename to ui/zoltar/ts/copy/securityPool.ts index 0ab81b507..a24543e63 100644 --- a/ui/ts/copy/securityPool.ts +++ b/ui/zoltar/ts/copy/securityPool.ts @@ -1,4 +1,4 @@ -import type { CopyTemplateValue } from './types.js' +import type { CopyTemplateValue } from '@zoltar/ui-core-shared/copy/types.js' export const annualFee = 'Annual Fee' export const badDebt = 'Bad Debt' diff --git a/ui/ts/copy/trading.ts b/ui/zoltar/ts/copy/trading.ts similarity index 100% rename from ui/ts/copy/trading.ts rename to ui/zoltar/ts/copy/trading.ts diff --git a/ui/ts/copy/zoltar.ts b/ui/zoltar/ts/copy/zoltar.ts similarity index 95% rename from ui/ts/copy/zoltar.ts rename to ui/zoltar/ts/copy/zoltar.ts index b41656aa3..d5679c02a 100644 --- a/ui/ts/copy/zoltar.ts +++ b/ui/zoltar/ts/copy/zoltar.ts @@ -1,5 +1,6 @@ -import type { CopyTemplateValue } from './types.js' +import type { CopyTemplateValue } from '@zoltar/ui-core-shared/copy/types.js' +export const applicationTitle = 'Zoltar' export const forkZoltar = 'Fork Universe' export const viewForkDetails = 'View fork details' export const viewForkDetailsTitle = 'View Fork Details' @@ -43,6 +44,7 @@ export const preparingCurrentAmountLabel = 'preparing the current amount' export const preparingRepPending = 'Preparing REP…' export const splittingRepPending = 'Splitting REP…' export const preparationNotForkedReason = 'REP preparation is unavailable because this universe has not forked.' +export const migrationNotForkedReason = 'REP migration is unavailable because this universe has not forked.' export const chooseDestinationStep = '1. Choose destinations' export const prepareRepStep = '2. Prepare REP' export const splitRepStep = '3. Split REP' diff --git a/ui/zoltar/ts/features/README.md b/ui/zoltar/ts/features/README.md new file mode 100644 index 000000000..a9a3e79e9 --- /dev/null +++ b/ui/zoltar/ts/features/README.md @@ -0,0 +1,9 @@ +# UI Features + +Feature directories own their route-specific components, hooks, and domain presentation logic. Keep code inside the feature that gives it meaning; move cross-app code to `ui/coreShared/ts` or Zoltar-specific shared code to `ui/zoltar/ts` only when it is genuinely reusable across features. + +Feature `lib` modules may own UI-agnostic calculations used only by that feature. If the protocol client also needs a calculation, place it in `ui/zoltar/ts/protocol` or the appropriate shared `lib`; protocol modules never import from feature directories. + +Protocol reads and writes belong in `ui/zoltar/ts/protocol`. Application-shell composition belongs in `ui/zoltar/ts/app`; features must not import it. Reusable presentational components and generic helpers belong in `ui/coreShared/ts`; those shared layers must not depend on either application. Simulation may depend on protocol and shared modules, but not feature presentation code. + +Tests mirror feature names under `ui/zoltar/ts/tests/features`, while app-shell tests live under `ui/zoltar/ts/tests/app`. Feature-specific test support stays with its feature instead of the global `tests/testUtils` directory. Protocol and simulation tests may not reach upward into feature or app ownership. Browser-local simulation is an intentional top-level subsystem in `ui/zoltar/ts/simulation`; shared simulation tests live under `ui/coreShared/ts/tests/simulation`, and Zoltar environment integration tests live under `ui/zoltar/ts/tests/integration`. diff --git a/ui/ts/features/deployment/components/DeploymentRouteContent.tsx b/ui/zoltar/ts/features/deployment/components/DeploymentRouteContent.tsx similarity index 76% rename from ui/ts/features/deployment/components/DeploymentRouteContent.tsx rename to ui/zoltar/ts/features/deployment/components/DeploymentRouteContent.tsx index c34630f90..291354588 100644 --- a/ui/ts/features/deployment/components/DeploymentRouteContent.tsx +++ b/ui/zoltar/ts/features/deployment/components/DeploymentRouteContent.tsx @@ -1,26 +1,40 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as deploymentCopy from '../../../copy/deployment.js' import type { ComponentChildren } from 'preact' import { useId } from 'preact/hooks' -import { LoadableValue } from '../../../components/LoadableValue.js' -import { LoadingText } from '../../../components/LoadingText.js' +import { LoadableValue } from '@zoltar/ui-core-shared/components/LoadableValue.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' import { DeploymentSection } from './DeploymentSection.js' -import { RouteHeader } from '../../../components/RouteHeader.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { buildRouteHref, getTopLevelRouteSearch, ZOLTAR_ROUTE } from '../../../lib/routing.js' -import { writeZoltarViewQueryParam } from '../../../lib/urlParams.js' +import { RouteHeader } from '@zoltar/ui-core-shared/components/RouteHeader.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { buildRouteHref, getRouteHash, getTopLevelRouteSearch } from '@zoltar/ui-core-shared/lib/routing.js' +import { writeZoltarViewQueryParam } from '@zoltar/ui-core-shared/lib/urlParams.js' import { findNextDeployableStep, getDeployNextMissingAvailability } from '../lib/deployment.js' import type { DeploymentRouteContentProps } from '../../types.js' -export function DeploymentRouteContent({ accountAddress, busyStepId, deploymentStateReady, deploymentStatusError, deployNextMissingPending, deploymentSections, deploymentStatuses, isLoadingDeploymentStatuses, isOnActiveAppChain, onDeploy, onDeployNextMissing, onRetryDeploymentStatus }: DeploymentRouteContentProps) { +export function DeploymentRouteContent({ + accountAddress, + busyStepId, + deploymentStateReady, + deploymentStatusError, + deployNextMissingPending, + deploymentSections, + deploymentStatuses, + isLoadingDeploymentStatuses, + isOnActiveAppChain, + deploymentCompleteHref, + onDeploy, + onDeployNextMissing, + onRetryDeploymentStatus, +}: DeploymentRouteContentProps) { const deploymentStatusReasonId = useId() const nextMissingStep = findNextDeployableStep(deploymentStatuses) const deployedContractCount = deploymentStatuses.filter(step => step.deployed).length const totalContractCount = deploymentStatuses.length const deploymentComplete = deploymentStateReady && !isLoadingDeploymentStatuses && totalContractCount > 0 && deployedContractCount === totalContractCount - const questionsHref = buildRouteHref(ZOLTAR_ROUTE, writeZoltarViewQueryParam(getTopLevelRouteSearch('zoltar'), 'questions')) + const completedHref = deploymentCompleteHref ?? buildRouteHref(getRouteHash('zoltar'), writeZoltarViewQueryParam(getTopLevelRouteSearch('zoltar'), 'questions')) const deployNextAvailability = deploymentStateReady ? getDeployNextMissingAvailability({ accountAddress, @@ -69,8 +83,8 @@ export function DeploymentRouteContent({ accountAddress, busyStepId, deploymentS title={deploymentCopy.deterministicContractDeployment} description={deploymentCopy.deploymentOverviewDetail} actions={ - deploymentComplete ? ( - + deploymentComplete && deploymentComplete !== undefined ? ( + {deploymentCopy.browseQuestions} ) : ( diff --git a/ui/ts/features/deployment/components/DeploymentSection.tsx b/ui/zoltar/ts/features/deployment/components/DeploymentSection.tsx similarity index 92% rename from ui/ts/features/deployment/components/DeploymentSection.tsx rename to ui/zoltar/ts/features/deployment/components/DeploymentSection.tsx index 833d9d222..3f5b35a7f 100644 --- a/ui/ts/features/deployment/components/DeploymentSection.tsx +++ b/ui/zoltar/ts/features/deployment/components/DeploymentSection.tsx @@ -1,9 +1,9 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as deploymentCopy from '../../../copy/deployment.js' import type { BadgeTone, DeploymentSectionProps } from '../../types.js' -import { Badge } from '../../../components/Badge.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' import { getDeploymentStepAvailability, getPrerequisiteLabel } from '../lib/deployment.js' type StepStatus = { diff --git a/ui/ts/features/deployment/hooks/useDeploymentFlow.ts b/ui/zoltar/ts/features/deployment/hooks/useDeploymentFlow.ts similarity index 89% rename from ui/ts/features/deployment/hooks/useDeploymentFlow.ts rename to ui/zoltar/ts/features/deployment/hooks/useDeploymentFlow.ts index 2bfc5ad31..07fee8660 100644 --- a/ui/ts/features/deployment/hooks/useDeploymentFlow.ts +++ b/ui/zoltar/ts/features/deployment/hooks/useDeploymentFlow.ts @@ -1,15 +1,15 @@ import { useSignal } from '@preact/signals' import type { Address, Hash } from '@zoltar/shared/ethereum' -import { createWalletWriteClient } from '../../../lib/clients.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { findNextDeployableStep, getPrerequisiteLabel } from '../lib/deployment.js' -import { formatWriteErrorMessage } from '../../../lib/errors.js' +import { formatWriteErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' import { createDeploymentSuccessPresentation, createDeploymentTransactionIntent } from '../../transactionPresentations.js' -import { requireWallet } from '../../../lib/requireWalletConnection.js' -import { assertActiveWallet } from '../../../lib/assertActiveWallet.js' +import { requireWallet } from '@zoltar/ui-core-shared/lib/requireWalletConnection.js' +import { assertActiveWallet } from '@zoltar/ui-core-shared/lib/assertActiveWallet.js' import type { WriteOperationsParameters } from '../../../types/app.js' -import type { DeploymentStatus, DeploymentStepId } from '../../../types/contracts.js' +import type { DeploymentStatus, DeploymentStepId } from '@zoltar/ui-core-shared/types/contracts.js' import { assertDeploymentStepRuntimeCode } from '../../../protocol/deployment.js' import { readWithRpcStateRetries, type RpcStateRetryWait } from '../../../protocol/core.js' diff --git a/ui/ts/features/deployment/lib/deployment.ts b/ui/zoltar/ts/features/deployment/lib/deployment.ts similarity index 95% rename from ui/ts/features/deployment/lib/deployment.ts rename to ui/zoltar/ts/features/deployment/lib/deployment.ts index 3ea944bae..09afb2e09 100644 --- a/ui/ts/features/deployment/lib/deployment.ts +++ b/ui/zoltar/ts/features/deployment/lib/deployment.ts @@ -1,7 +1,7 @@ import * as deploymentCopy from '../../../copy/deployment.js' -import { getWalletActiveAppChainActionAvailability } from '../../../lib/actionGuards.js' +import { getWalletActiveAppChainActionAvailability } from '@zoltar/ui-core-shared/lib/actionGuards.js' import type { ActionAvailability } from '../../types.js' -import type { DeploymentStatus } from '../../../types/contracts.js' +import type { DeploymentStatus } from '@zoltar/ui-core-shared/types/contracts.js' type DeploymentStepAvailabilityState = Pick diff --git a/ui/ts/features/open-oracle/components/OpenOraclePriceValue.tsx b/ui/zoltar/ts/features/open-oracle/components/OpenOraclePriceValue.tsx similarity index 86% rename from ui/ts/features/open-oracle/components/OpenOraclePriceValue.tsx rename to ui/zoltar/ts/features/open-oracle/components/OpenOraclePriceValue.tsx index f9c7bbf11..efabeccc1 100644 --- a/ui/ts/features/open-oracle/components/OpenOraclePriceValue.tsx +++ b/ui/zoltar/ts/features/open-oracle/components/OpenOraclePriceValue.tsx @@ -1,6 +1,6 @@ -import * as commonCopy from '../../../copy/common.js' -import { useChainTimestamp } from '../../../lib/chainTimestamp.js' -import { getOracleLastPriceDisplay, getOraclePriceValidityPresentation } from '../../security-pools/lib/securityPoolWorkflow.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import { useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { getOracleLastPriceDisplay, getOraclePriceValidityPresentation } from '../lib/openOracle.js' type OpenOraclePriceValueProps = { currentTimestamp?: bigint | undefined diff --git a/ui/ts/features/open-oracle/components/OpenOracleSection.tsx b/ui/zoltar/ts/features/open-oracle/components/OpenOracleSection.tsx similarity index 95% rename from ui/ts/features/open-oracle/components/OpenOracleSection.tsx rename to ui/zoltar/ts/features/open-oracle/components/OpenOracleSection.tsx index 65463a8ff..208d3d037 100644 --- a/ui/ts/features/open-oracle/components/OpenOracleSection.tsx +++ b/ui/zoltar/ts/features/open-oracle/components/OpenOracleSection.tsx @@ -1,38 +1,38 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as openOracleCopy from '../../../copy/openOracle.js' -import * as transactionReviewCopy from '../../../copy/transactionReview.js' +import * as transactionReviewCopy from '@zoltar/ui-core-shared/copy/transactionReview.js' import { useEffect, useState } from 'preact/hooks' import type { ComponentChildren } from 'preact' import { zeroAddress } from '@zoltar/shared/ethereum' -import { ActionLauncherCard } from '../../../components/ActionLauncherCard.js' -import { AddressValue } from '../../../components/AddressValue.js' -import { Badge } from '../../../components/Badge.js' -import { ComparisonRecord } from '../../../components/ComparisonRecord.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { EnumDropdown, type EnumDropdownOption } from '../../../components/EnumDropdown.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { LifecycleStageBanner } from '../../security-pools/components/LifecycleStageBanner.js' -import { LookupFieldRow } from '../../../components/LookupFieldRow.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { MetricField } from '../../../components/MetricField.js' -import { OperationModal } from '../../../components/OperationModal.js' -import { PaginationControls } from '../../../components/PaginationControls.js' -import { ReadOnlyDetailAccordion } from '../../../components/ReadOnlyDetailAccordion.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { StickyObjectContext } from '../../../components/StickyObjectContext.js' -import { StateHint } from '../../../components/StateHint.js' -import { TokenApprovalControl } from '../../../components/TokenApprovalControl.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionNetworkValue } from '../../../components/TransactionNetworkValue.js' -import { TransactionObjectContext } from '../../../components/TransactionObjectContext.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { TimestampValue } from '../../../components/TimestampValue.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { assertNever } from '../../../lib/assert.js' -import { createConnectedReadClient } from '../../../lib/clients.js' -import { useChainBlockNumber, useChainTimestamp } from '../../../lib/chainTimestamp.js' +import { ActionLauncherCard } from '@zoltar/ui-core-shared/components/ActionLauncherCard.js' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { ComparisonRecord } from '@zoltar/ui-core-shared/components/ComparisonRecord.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { EnumDropdown, type EnumDropdownOption } from '@zoltar/ui-core-shared/components/EnumDropdown.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { LifecycleStageBanner } from '@zoltar/ui-core-shared/components/LifecycleStageBanner.js' +import { LookupFieldRow } from '@zoltar/ui-core-shared/components/LookupFieldRow.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { OperationModal } from '@zoltar/ui-core-shared/components/OperationModal.js' +import { PaginationControls } from '@zoltar/ui-core-shared/components/PaginationControls.js' +import { ReadOnlyDetailAccordion } from '@zoltar/ui-core-shared/components/ReadOnlyDetailAccordion.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { StickyObjectContext } from '@zoltar/ui-core-shared/components/StickyObjectContext.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { TokenApprovalControl } from '@zoltar/ui-core-shared/components/TokenApprovalControl.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionNetworkValue } from '@zoltar/ui-core-shared/components/TransactionNetworkValue.js' +import { TransactionObjectContext } from '@zoltar/ui-core-shared/components/TransactionObjectContext.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { createConnectedReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { useChainBlockNumber, useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' import { getOpenOracleCreateGuardMessage, getOpenOracleCreateValidation, @@ -51,14 +51,14 @@ import { } from '../lib/openOracle.js' import { getOpenOracleReadinessActions } from '../lib/openOracleReadiness.js' import { getOpenOracleStagePresentation } from '../lib/openOracleStage.js' -import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex } from '../../../lib/pagination.js' +import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex } from '@zoltar/ui-core-shared/lib/pagination.js' import { loadOpenOracleReportSummaries } from '../../../protocol/index.js' -import { getWrongNetworkMessage, isActiveAppChain } from '../../../lib/network.js' -import { tryParseBigIntInput } from '../../../lib/integerInput.js' -import { getReportPresentation } from '../../../lib/userCopy.js' -import { formatCurrencyInputBalance, formatDuration, formatValueWithUnit } from '../../../lib/formatters.js' +import { getWrongNetworkMessage, isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { tryParseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { getReportPresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import { formatCurrencyInputBalance, formatDuration, formatValueWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' import type { OpenOracleFormState } from '../../../types/app.js' -import type { OpenOracleReportDetails, OpenOracleReportSummary, OpenOracleReportSummaryPage, OpenOracleWithdrawableBalances } from '../../../types/contracts.js' +import type { OpenOracleReportDetails, OpenOracleReportSummary, OpenOracleReportSummaryPage, OpenOracleWithdrawableBalances } from '@zoltar/ui-core-shared/types/contracts.js' import type { OpenOracleSectionProps } from '../../types.js' const BROWSE_PAGE_SIZE = 10 const OPEN_ORACLE_PRICE_UNITS = 30 diff --git a/ui/ts/features/open-oracle/hooks/useOpenOracleOperations.ts b/ui/zoltar/ts/features/open-oracle/hooks/useOpenOracleOperations.ts similarity index 97% rename from ui/ts/features/open-oracle/hooks/useOpenOracleOperations.ts rename to ui/zoltar/ts/features/open-oracle/hooks/useOpenOracleOperations.ts index 27681d568..5fb390839 100644 --- a/ui/ts/features/open-oracle/hooks/useOpenOracleOperations.ts +++ b/ui/zoltar/ts/features/open-oracle/hooks/useOpenOracleOperations.ts @@ -1,13 +1,13 @@ import { useSignal } from '@preact/signals' import { bigintToSafeNumber, zeroAddress, type Abi, type Address, type Hash } from '@zoltar/shared/ethereum' import { useEffect, useRef } from 'preact/hooks' -import { useFormState } from '../../../hooks/useFormState.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { ABIS } from '../../../abis.js' +import { useFormState } from '@zoltar/ui-core-shared/hooks/useFormState.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' import { approveErc20, createOpenOracleReportInstance, disputeOracleReport, getOpenOracleAddress, isOpenOracleReportMissingError, loadOpenOracleReportDetails, loadOpenOracleWithdrawableBalances, readOptionalMulticall, settleOracleReport, withdrawOpenOracleBalance } from '../../../protocol/index.js' -import { assertNever } from '../../../lib/assert.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { getErrorMessage, isRecoverableContractReadError } from '../../../lib/errors.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getErrorMessage, isRecoverableContractReadError } from '@zoltar/ui-core-shared/lib/errors.js' import { deriveOpenOracleDisputeSubmissionDetails, formatOpenOracleDisputeWriteErrorMessage, @@ -19,18 +19,18 @@ import { parseOpenOracleCreateFormSubmission, } from '../lib/openOracle.js' import type { OpenOracleCreateContractFieldErrors } from '../lib/openOracle.js' -import { parseAddressInput, parseReportIdInput } from '../../../lib/inputs.js' -import { getDefaultOpenOracleCreateFormState, getDefaultOpenOracleFormState } from '../../markets/lib/marketForm.js' -import { requireDefined } from '../../../lib/required.js' -import { formatTokenApprovalUnavailableMessage, type TokenApprovalRequirement, type TokenApprovalState } from '../../../lib/tokenApproval.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { parseAddressInput, parseReportIdInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { getDefaultOpenOracleCreateFormState, getDefaultOpenOracleFormState } from '../../../lib/formDefaults.js' +import { requireDefined } from '@zoltar/ui-core-shared/lib/required.js' +import { formatTokenApprovalUnavailableMessage, type TokenApprovalRequirement, type TokenApprovalState } from '@zoltar/ui-core-shared/lib/tokenApproval.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createOpenOracleSuccessPresentation, createOpenOracleTransactionIntent, createOpenOracleWarningPresentation } from '../../transactionPresentations.js' -import { buildWriteActionConfig, runWriteAction } from '../../../lib/writeAction.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { buildWriteActionConfig, runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { OpenOracleCreateFormState, OpenOracleFormState, WriteOperationsParameters } from '../../../types/app.js' -import type { OpenOracleActionResult, OpenOracleReportDetails, OpenOracleWithdrawableBalances } from '../../../types/contracts.js' +import type { OpenOracleActionResult, OpenOracleReportDetails, OpenOracleWithdrawableBalances } from '@zoltar/ui-core-shared/types/contracts.js' import type { OpenOracleReportLookupState } from '../../types.js' import * as openOracleCopy from '../../../copy/openOracle.js' @@ -176,7 +176,8 @@ function useOpenOracleOperationsWithDependencies( const openOracleCreateFieldErrors = useSignal({}) const openOracleError = useSignal(undefined) const openOracleActiveAction = useSignal(undefined) - const openOracleActiveWithdrawalBalance = useSignal(undefined) + type OpenOracleWithdrawableBalanceKey = 'ethAttoEth' | 'token1' | 'token2' + const openOracleActiveWithdrawalBalance = useSignal(undefined) const openOracleFeedback = useSignal | undefined>(undefined) const { state: openOracleForm, setState: setOpenOracleFormState } = useFormState(getDefaultOpenOracleFormState()) const openOracleResult = useSignal(undefined) diff --git a/ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts b/ui/zoltar/ts/features/open-oracle/hooks/usePriceOracleManager.ts similarity index 94% rename from ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts rename to ui/zoltar/ts/features/open-oracle/hooks/usePriceOracleManager.ts index 63cd7f177..d55724994 100644 --- a/ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts +++ b/ui/zoltar/ts/features/open-oracle/hooks/usePriceOracleManager.ts @@ -1,19 +1,19 @@ import { useSignal } from '@preact/signals' import type { Address, Hash } from '@zoltar/shared/ethereum' import { executeOracleManagerStagedOperation, loadCoordinatorInitialReportFundingRequirement, loadOracleManagerDetails, requestOraclePrice } from '../../../protocol/index.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { getErrorMessage } from '../../../lib/errors.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { getOracleRequestEthGuardMessage } from '../lib/oracleRequestEth.js' -import { formatAdditionalCurrencyBalance } from '../../../lib/formatters.js' +import { formatAdditionalCurrencyBalance } from '@zoltar/ui-core-shared/lib/formatters.js' import { createPoolOracleSuccessPresentation, createPoolOracleTransactionIntent, createPoolOracleWarningPresentation } from '../../transactionPresentations.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { runWriteAction } from '../../../lib/writeAction.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { WriteOperationsParameters } from '../../../types/app.js' -import type { OpenOracleActionResult, OracleManagerDetails } from '../../../types/contracts.js' +import type { OpenOracleActionResult, OracleManagerDetails } from '@zoltar/ui-core-shared/types/contracts.js' type UsePriceOracleManagerParameters = { accountAddress: Address | undefined diff --git a/ui/ts/features/open-oracle/hooks/useRepPrices.ts b/ui/zoltar/ts/features/open-oracle/hooks/useRepPrices.ts similarity index 87% rename from ui/ts/features/open-oracle/hooks/useRepPrices.ts rename to ui/zoltar/ts/features/open-oracle/hooks/useRepPrices.ts index cd1630e3e..8ff62f078 100644 --- a/ui/ts/features/open-oracle/hooks/useRepPrices.ts +++ b/ui/zoltar/ts/features/open-oracle/hooks/useRepPrices.ts @@ -1,11 +1,32 @@ import { useSignal } from '@preact/signals' import { useEffect, useRef } from 'preact/hooks' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { getActiveBackend } from '../../../lib/activeEnvironment.js' -import type { ChainBackend } from '../../../lib/chainBackend.js' -import { isRecoverableQuoteError } from '../../../lib/errors.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { getActiveBackend } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import type { ChainBackend } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import { isRecoverableQuoteError } from '@zoltar/ui-core-shared/lib/errors.js' import { quoteBestExactInputWithSource, quoteBestV3ExactInputWithSource, quoteRepForUsdcV4WithSource, ETH_ADDRESS, getRepAddress, isRepPricingEnabled } from '../../../protocol/uniswapQuoter.js' + +type RepPriceQuoter = { + getRepAddress: typeof getRepAddress + isRepPricingEnabled: typeof isRepPricingEnabled + quoteBestExactInputWithSource: typeof quoteBestExactInputWithSource + quoteBestV3ExactInputWithSource: typeof quoteBestV3ExactInputWithSource + quoteRepForUsdcV4WithSource: typeof quoteRepForUsdcV4WithSource +} + +declare global { + // eslint-disable-next-line no-var + var __zoltarRepPriceQuoterOverride__: RepPriceQuoter | undefined +} + +export function installRepPriceQuoterForTesting(quoter: RepPriceQuoter | undefined) { + globalThis.__zoltarRepPriceQuoterOverride__ = quoter +} + +function getRepPriceQuoter(): RepPriceQuoter { + return globalThis.__zoltarRepPriceQuoterOverride__ ?? { getRepAddress, isRepPricingEnabled, quoteBestExactInputWithSource, quoteBestV3ExactInputWithSource, quoteRepForUsdcV4WithSource } +} import type { RepPriceFailure } from '../../types.js' const ATTO_ETH_PER_ETH = 10n ** 18n @@ -80,14 +101,15 @@ export function resetRepPriceCacheForTesting() { } async function fetchRepPerEthPrice(client: ReturnType): Promise<{ price: bigint; source: PriceSource; sourceUrl: string | undefined }> { - const repAddress = getRepAddress() + const quoter = getRepPriceQuoter() + const repAddress = quoter.getRepAddress() try { - const { amountOut, source } = await quoteBestExactInputWithSource(client, ETH_ADDRESS, repAddress, ATTO_ETH_PER_ETH) + const { amountOut, source } = await quoter.quoteBestExactInputWithSource(client, ETH_ADDRESS, repAddress, ATTO_ETH_PER_ETH) return { price: amountOut, source: source.protocol === 'mock' ? 'mock' : 'v4', sourceUrl: source.poolUrl } } catch (error) { if (!isRecoverableQuoteError(error)) throw error // V4 REP/ETH pool doesn't exist yet — fall back to V3 WETH/REP (1% pool) - const { amountOut, source } = await quoteBestV3ExactInputWithSource(client, ETH_ADDRESS, repAddress, ATTO_ETH_PER_ETH) + const { amountOut, source } = await quoter.quoteBestV3ExactInputWithSource(client, ETH_ADDRESS, repAddress, ATTO_ETH_PER_ETH) return { price: amountOut, source: source.protocol === 'mock' ? 'mock' : 'v3', sourceUrl: source.poolUrl } } } @@ -117,7 +139,7 @@ async function loadRepPrices(backend: ChainBackend, forceRefresh: boolean) { repPriceRefreshGenerationByBackend.set(backend, refreshGeneration) const refreshPromise = (async () => { const client = backend.createReadClient() - const [repPerEthResult, repUsdcResult] = await Promise.allSettled([fetchRepPerEthPrice(client), quoteRepForUsdcV4WithSource(client, ATTO_REP)]) + const [repPerEthResult, repUsdcResult] = await Promise.allSettled([fetchRepPerEthPrice(client), getRepPriceQuoter().quoteRepForUsdcV4WithSource(client, ATTO_REP)]) if (repPerEthResult.status === 'rejected' && !isRecoverableQuoteError(repPerEthResult.reason)) throw repPerEthResult.reason if (repUsdcResult.status === 'rejected' && !isRecoverableQuoteError(repUsdcResult.reason)) throw repUsdcResult.reason const nextCachedRepPrices: CachedRepPrices = { @@ -146,7 +168,7 @@ async function loadRepPrices(backend: ChainBackend, forceRefresh: boolean) { nextCachedRepPrices.repUsdcSource = repUsdcResult.value.source.protocol === 'mock' ? 'mock' : 'v4' nextCachedRepPrices.repUsdcSourceUrl = repUsdcResult.value.source.poolUrl hasNextCachedRepPrices = true - } else if (!isRepPricingEnabled()) { + } else if (!getRepPriceQuoter().isRepPricingEnabled()) { nextCachedRepPrices.repUsdcCachedAtMs = Date.now() nextCachedRepPrices.repUsdcPrice = undefined nextCachedRepPrices.repUsdcSource = undefined diff --git a/ui/ts/features/open-oracle/lib/openOracle.ts b/ui/zoltar/ts/features/open-oracle/lib/openOracle.ts similarity index 93% rename from ui/ts/features/open-oracle/lib/openOracle.ts rename to ui/zoltar/ts/features/open-oracle/lib/openOracle.ts index 39c2593b2..04e0ad7e3 100644 --- a/ui/ts/features/open-oracle/lib/openOracle.ts +++ b/ui/zoltar/ts/features/open-oracle/lib/openOracle.ts @@ -1,15 +1,17 @@ import { bigintToSafeNumber, zeroAddress, type Address } from '@zoltar/shared/ethereum' import type { OpenOracleCreateFormState } from '../../../types/app.js' -import type { OpenOracleReportDetails, OpenOracleReportSummary } from '../../../types/contracts.js' -import { getWalletConnectionActiveAppChainGuardState } from '../../../lib/actionGuards.js' -import { sameAddress } from '../../../lib/address.js' -import { assertNever } from '../../../lib/assert.js' -import { parseDecimalInput, tryParseDecimalInput } from '../../../lib/decimal.js' -import { formatWriteErrorMessage, getErrorDetail, sanitizeErrorDetail } from '../../../lib/errors.js' -import { formatAdditionalCurrencyBalance, formatCurrencyBalance, formatCurrencyInputBalance, formatDuration } from '../../../lib/formatters.js' -import { parseAddressInput, tryParseAddressInput } from '../../../lib/inputs.js' -import { parseBigIntInput, tryParseBigIntInput } from '../../markets/lib/marketForm.js' -import { deriveTokenApprovalRequirement, formatTokenApprovalUnavailableMessage, type TokenApprovalRequirement } from '../../../lib/tokenApproval.js' +import type { OpenOracleReportDetails, OpenOracleReportSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWalletConnectionActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { parseDecimalInput, tryParseDecimalInput } from '@zoltar/ui-core-shared/lib/decimal.js' +import { formatWriteErrorMessage, getErrorDetail, sanitizeErrorDetail } from '@zoltar/ui-core-shared/lib/errors.js' +import { formatAdditionalCurrencyBalance, formatCurrencyBalance, formatCurrencyInputBalance, formatDuration, formatRoundedCurrencyBalance } from '@zoltar/ui-core-shared/lib/formatters.js' +import { getTimeRemaining } from '@zoltar/ui-core-shared/lib/time.js' +import { getOracleManagerPriceValidUntilTimestamp } from '../../../protocol/oracleTiming.js' +import { parseAddressInput, tryParseAddressInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { parseBigIntInput, tryParseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import { deriveTokenApprovalRequirement, formatTokenApprovalUnavailableMessage, type TokenApprovalRequirement } from '@zoltar/ui-core-shared/lib/tokenApproval.js' import { addOpenOracleBountyBuffer, getOpenOracleDisputeSwapTokenKey } from '../../../protocol/openOracleMath.js' import { getOpenOracleCreateParameterValidation, OPEN_ORACLE_MULTIPLIER_PRECISION, OPEN_ORACLE_PERCENTAGE_PRECISION } from '../../../protocol/openOracleValidation.js' const OPEN_ORACLE_DECIMAL_INPUT_PATTERN = /^-?(?:\d+\.?\d*|\.\d+)$/ @@ -662,3 +664,21 @@ export function deriveOpenOracleDisputeSubmissionDetails({ token2Decimals, } } + +export function getOracleLastPriceDisplay({ lastPrice, lastSettlementTimestamp }: { lastPrice: bigint; lastSettlementTimestamp: bigint }) { + if (lastSettlementTimestamp === 0n) return '-' + return `≈ ${formatRoundedCurrencyBalance(lastPrice, 18, 2)}\u00a0REP / ETH` +} + +export function getOraclePriceValidityPresentation({ currentTimestamp, lastSettlementTimestamp, priceValidUntilTimestamp }: { currentTimestamp: bigint; lastSettlementTimestamp: bigint; priceValidUntilTimestamp: bigint | undefined }) { + if (lastSettlementTimestamp === 0n) return undefined + const validUntilTimestamp = priceValidUntilTimestamp ?? getOracleManagerPriceValidUntilTimestamp(lastSettlementTimestamp) + if (validUntilTimestamp === undefined) return undefined + const timeRemaining = getTimeRemaining(validUntilTimestamp, currentTimestamp) + if (timeRemaining === undefined) return undefined + if (timeRemaining === 0n) { + const expiredFor = currentTimestamp > validUntilTimestamp ? currentTimestamp - validUntilTimestamp : 0n + return { text: `(expired ${expiredFor === 0n ? 'less than a minute' : formatDuration(expiredFor)} ago)`, tone: 'danger' as const } + } + return { text: `(Valid for ${formatDuration(timeRemaining)})`, tone: 'success' as const } +} diff --git a/ui/ts/features/open-oracle/lib/openOracleReadiness.ts b/ui/zoltar/ts/features/open-oracle/lib/openOracleReadiness.ts similarity index 100% rename from ui/ts/features/open-oracle/lib/openOracleReadiness.ts rename to ui/zoltar/ts/features/open-oracle/lib/openOracleReadiness.ts diff --git a/ui/ts/features/open-oracle/lib/openOracleStage.ts b/ui/zoltar/ts/features/open-oracle/lib/openOracleStage.ts similarity index 89% rename from ui/ts/features/open-oracle/lib/openOracleStage.ts rename to ui/zoltar/ts/features/open-oracle/lib/openOracleStage.ts index 7c04cdcb9..967931295 100644 --- a/ui/ts/features/open-oracle/lib/openOracleStage.ts +++ b/ui/zoltar/ts/features/open-oracle/lib/openOracleStage.ts @@ -1,8 +1,8 @@ import type { OpenOracleSelectedReportActionMode } from './openOracle.js' -import { assertNever } from '../../../lib/assert.js' -import { formatDuration } from '../../../lib/formatters.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { formatDuration } from '@zoltar/ui-core-shared/lib/formatters.js' import type { LifecycleStagePresentation } from '../../types.js' -import type { OpenOracleReportDetails } from '../../../types/contracts.js' +import type { OpenOracleReportDetails } from '@zoltar/ui-core-shared/types/contracts.js' type OpenOracleStageReport = Pick diff --git a/ui/ts/features/open-oracle/lib/oracleRequestEth.ts b/ui/zoltar/ts/features/open-oracle/lib/oracleRequestEth.ts similarity index 92% rename from ui/ts/features/open-oracle/lib/oracleRequestEth.ts rename to ui/zoltar/ts/features/open-oracle/lib/oracleRequestEth.ts index 1c723599e..5c2cf4b1f 100644 --- a/ui/ts/features/open-oracle/lib/oracleRequestEth.ts +++ b/ui/zoltar/ts/features/open-oracle/lib/oracleRequestEth.ts @@ -1,4 +1,4 @@ -import { formatAdditionalCurrencyBalance } from '../../../lib/formatters.js' +import { formatAdditionalCurrencyBalance } from '@zoltar/ui-core-shared/lib/formatters.js' import { addOpenOracleBountyBuffer } from '../../../protocol/openOracleMath.js' import { resolveOracleOperationEthFunding } from '../../../protocol/oracleRequestFunding.js' diff --git a/ui/ts/features/open-oracle/lib/repPriceSource.tsx b/ui/zoltar/ts/features/open-oracle/lib/repPriceSource.tsx similarity index 91% rename from ui/ts/features/open-oracle/lib/repPriceSource.tsx rename to ui/zoltar/ts/features/open-oracle/lib/repPriceSource.tsx index b8d7f81ac..8254e2955 100644 --- a/ui/ts/features/open-oracle/lib/repPriceSource.tsx +++ b/ui/zoltar/ts/features/open-oracle/lib/repPriceSource.tsx @@ -1,6 +1,6 @@ -import * as commonCopy from '../../../copy/common.js' -import * as pricingCopy from '../../../copy/pricing.js' -import { assertNever } from '../../../lib/assert.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as pricingCopy from '@zoltar/ui-core-shared/copy/pricing.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' export type RepPriceSource = 'v4' | 'v3' | 'mock' diff --git a/ui/ts/features/reporting/components/EscalationDepositSelectionList.tsx b/ui/zoltar/ts/features/reporting/components/EscalationDepositSelectionList.tsx similarity index 92% rename from ui/ts/features/reporting/components/EscalationDepositSelectionList.tsx rename to ui/zoltar/ts/features/reporting/components/EscalationDepositSelectionList.tsx index 515223c65..9a66b3546 100644 --- a/ui/ts/features/reporting/components/EscalationDepositSelectionList.tsx +++ b/ui/zoltar/ts/features/reporting/components/EscalationDepositSelectionList.tsx @@ -1,10 +1,10 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as forkAuctionCopy from '../../../copy/forkAuction.js' import type { ComponentChildren } from 'preact' import { useEffect, useMemo, useState } from 'preact/hooks' -import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex } from '../../../lib/pagination.js' -import type { EscalationDeposit } from '../../../types/contracts.js' -import { PaginationControls } from '../../../components/PaginationControls.js' +import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex } from '@zoltar/ui-core-shared/lib/pagination.js' +import type { EscalationDeposit } from '@zoltar/ui-core-shared/types/contracts.js' +import { PaginationControls } from '@zoltar/ui-core-shared/components/PaginationControls.js' const ESCALATION_DEPOSIT_SELECTION_PAGE_SIZE = 25 diff --git a/ui/ts/features/reporting/components/EscalationSide.tsx b/ui/zoltar/ts/features/reporting/components/EscalationSide.tsx similarity index 93% rename from ui/ts/features/reporting/components/EscalationSide.tsx rename to ui/zoltar/ts/features/reporting/components/EscalationSide.tsx index 83fa8556b..38a1d31b0 100644 --- a/ui/ts/features/reporting/components/EscalationSide.tsx +++ b/ui/zoltar/ts/features/reporting/components/EscalationSide.tsx @@ -1,9 +1,9 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as forkAuctionCopy from '../../../copy/forkAuction.js' import type { JSX } from 'preact' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { Badge } from '../../../components/Badge.js' -import type { EscalationDeposit } from '../../../types/contracts.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import type { EscalationDeposit } from '@zoltar/ui-core-shared/types/contracts.js' type EscalationSideDisplay = { balance: bigint | undefined diff --git a/ui/ts/features/reporting/components/ImportedForkSettlementSection.tsx b/ui/zoltar/ts/features/reporting/components/ImportedForkSettlementSection.tsx similarity index 93% rename from ui/ts/features/reporting/components/ImportedForkSettlementSection.tsx rename to ui/zoltar/ts/features/reporting/components/ImportedForkSettlementSection.tsx index 604e68568..d610d977b 100644 --- a/ui/ts/features/reporting/components/ImportedForkSettlementSection.tsx +++ b/ui/zoltar/ts/features/reporting/components/ImportedForkSettlementSection.tsx @@ -1,13 +1,13 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as forkAuctionCopy from '../../../copy/forkAuction.js' import type { ComponentChildren } from 'preact' import { useEffect, useMemo, useState } from 'preact/hooks' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { PaginationControls } from '../../../components/PaginationControls.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex } from '../../../lib/pagination.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { PaginationControls } from '@zoltar/ui-core-shared/components/PaginationControls.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { formatPaginationSummary, getHasNextPaginationPage, getPaginationPageCount, resolvePaginationPageIndex } from '@zoltar/ui-core-shared/lib/pagination.js' import { getImportedEscalationDepositClaimAmount } from '../lib/reportingDomain.js' -import type { ActiveReportingDetails, EscalationSide, ReportingOutcomeKey } from '../../../types/contracts.js' +import type { ActiveReportingDetails, EscalationSide, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' const IMPORTED_FORK_SETTLEMENT_PAGE_SIZE = 25 diff --git a/ui/ts/features/reporting/components/ReportingSection.tsx b/ui/zoltar/ts/features/reporting/components/ReportingSection.tsx similarity index 94% rename from ui/ts/features/reporting/components/ReportingSection.tsx rename to ui/zoltar/ts/features/reporting/components/ReportingSection.tsx index 52d3ec3bf..0f02dbb81 100644 --- a/ui/ts/features/reporting/components/ReportingSection.tsx +++ b/ui/zoltar/ts/features/reporting/components/ReportingSection.tsx @@ -1,26 +1,26 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as reportingCopy from '../../../copy/reporting.js' import { useEffect, useId, useRef, useState } from 'preact/hooks' -import { CurrencyValue } from '../../../components/CurrencyValue.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' import { EscalationDepositSelectionList } from './EscalationDepositSelectionList.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' import { EscalationSide } from './EscalationSide.js' -import { LifecycleStageBanner } from '../../security-pools/components/LifecycleStageBanner.js' -import { LookupFieldRow } from '../../../components/LookupFieldRow.js' -import { LoadingAwareText, LoadingText } from '../../../components/LoadingText.js' -import { MetricField } from '../../../components/MetricField.js' -import { RouteWorkflowPanel } from '../../../components/RouteWorkflowPanel.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { TimestampValue } from '../../../components/TimestampValue.js' -import { WarningSurface } from '../../../components/WarningSurface.js' -import { assertNever } from '../../../lib/assert.js' -import { pickFirstReason } from '../../../lib/actionAvailability.js' -import { formatCurrencyInputBalance, formatDuration } from '../../../lib/formatters.js' -import { parseOptionalRepAmountInput } from '../../markets/lib/marketForm.js' -import { getWrongNetworkMessage, isActiveAppChain } from '../../../lib/network.js' +import { LifecycleStageBanner } from '@zoltar/ui-core-shared/components/LifecycleStageBanner.js' +import { LookupFieldRow } from '@zoltar/ui-core-shared/components/LookupFieldRow.js' +import { LoadingAwareText, LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { RouteWorkflowPanel } from '@zoltar/ui-core-shared/components/RouteWorkflowPanel.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' +import { WarningSurface } from '@zoltar/ui-core-shared/components/WarningSurface.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { pickFirstReason } from '@zoltar/ui-core-shared/lib/actionAvailability.js' +import { formatCurrencyInputBalance, formatDuration } from '@zoltar/ui-core-shared/lib/formatters.js' +import { parseOptionalRepAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { getWrongNetworkMessage, isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' import { calculateEstimatedEscalationReturn, ESCALATION_GAME_ACTIVATION_DELAY, @@ -38,9 +38,9 @@ import { } from '../lib/reportingDomain.js' import { getReportingReportGuardMessage, getReportingWithdrawGuardMessage } from '../lib/reportingGuards.js' import { REPORTING_OUTCOME_DROPDOWN_OPTIONS, getReportingLockedUntilMessage, getReportingOutcomeLabel, hasReportingOpened } from '../lib/reporting.js' -import { deriveSecurityPoolReportingStage, evaluateSecurityPoolState } from '../../security-pools/lib/securityPoolState.js' +import { deriveReportingStage, isReportingOutcomeEnabled, isWithdrawEscalationEnabled } from '../lib/reporting.js' import type { LifecycleStagePresentation, ReportingSectionProps } from '../../types.js' -import type { EscalationDeposit, ReportingDetails, ReportingOutcomeKey } from '../../../types/contracts.js' +import type { EscalationDeposit, ReportingDetails, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' type ReportingStatus = 'active' | 'missing' | 'not-started' type EscalationSideDisplay = { balance: bigint | undefined @@ -237,16 +237,12 @@ export function ReportingSection({ const showSettlementSection = showFullReporting || showWithdrawOnly const reportingReady = marketDetails === undefined ? undefined : hasReportingOpened(marketDetails.endTime, effectiveCurrentTimestamp) const preOpenLockedReason = lockedReason ?? (reportingReady === false && marketDetails !== undefined && effectiveCurrentTimestamp !== undefined ? getReportingLockedUntilMessage(marketDetails.endTime, effectiveCurrentTimestamp) : undefined) - const reportingStageKey = deriveSecurityPoolReportingStage({ + const reportingStageKey = deriveReportingStage({ reportingDetails: effectiveReportingDetails, reportingReady, }) - const reportingState = evaluateSecurityPoolState({ - reportingStage: reportingStageKey, - universeHasForked: false, - }) - const reportOutcomeEnabled = reportingState.actions.reportOutcome.enabled - const withdrawEscalationEnabled = reportingState.actions.withdrawEscalation.enabled + const reportOutcomeEnabled = isReportingOutcomeEnabled(reportingStageKey) + const withdrawEscalationEnabled = isWithdrawEscalationEnabled(reportingStageKey) let reportLifecycleReason: string | undefined if (reportingStageKey === 'forkTriggered') { reportLifecycleReason = forkAlreadyTriggered ? FORK_ALREADY_TRIGGERED_REPORT_REASON : FORK_TRIGGERED_REPORT_REASON @@ -256,7 +252,7 @@ export function ReportingSection({ reportLifecycleReason = reportingCopy.poolFinalizedReason } const fullReportingLoadingReason = showFullReporting && loadingReportingDetails ? reportingCopy.reportingDetailsRequired : undefined - const reportControlsLockedReason = showFullReporting ? pickFirstReason(fullReportingLoadingReason, lockedReason, reportingState.reportingStage === 'preOpen' ? preOpenLockedReason : undefined, reportLifecycleReason) : preOpenLockedReason + const reportControlsLockedReason = showFullReporting ? pickFirstReason(fullReportingLoadingReason, lockedReason, reportingStageKey === 'preOpen' ? preOpenLockedReason : undefined, reportLifecycleReason) : preOpenLockedReason const reportControlsLocked = !reportOutcomeEnabled || reportControlsLockedReason !== undefined let settlementLifecycleReason: string | undefined if (reportingStageKey === 'forkTriggered') { @@ -274,7 +270,7 @@ export function ReportingSection({ if (showSettlementSection && loadingReportingDetails) { withdrawControlsLockedReason = showFullReporting ? reportingCopy.reportingDetailsRequired : reportingCopy.loadingEscalationDeposits } else { - withdrawControlsLockedReason = pickFirstReason(lockedReason, reportingState.reportingStage === 'preOpen' ? preOpenLockedReason : undefined, settlementLifecycleReason) + withdrawControlsLockedReason = pickFirstReason(lockedReason, reportingStageKey === 'preOpen' ? preOpenLockedReason : undefined, settlementLifecycleReason) } let settlementContextMessage: string | undefined if (activeReportingDetails?.settlementState === 'migration-required') settlementContextMessage = forkAlreadyTriggered ? reportingCopy.continueForkMigrationDetail : reportingCopy.forkMigrationRequiredDetail diff --git a/ui/ts/features/reporting/hooks/useReportingOperations.ts b/ui/zoltar/ts/features/reporting/hooks/useReportingOperations.ts similarity index 93% rename from ui/ts/features/reporting/hooks/useReportingOperations.ts rename to ui/zoltar/ts/features/reporting/hooks/useReportingOperations.ts index 88f105265..75b1ed087 100644 --- a/ui/ts/features/reporting/hooks/useReportingOperations.ts +++ b/ui/zoltar/ts/features/reporting/hooks/useReportingOperations.ts @@ -1,24 +1,25 @@ import { useSignal } from '@preact/signals' import { useRef } from 'preact/hooks' -import { useFormState } from '../../../hooks/useFormState.js' -import { useLoadController } from '../../../hooks/useLoadController.js' +import { useFormState } from '@zoltar/ui-core-shared/hooks/useFormState.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' import type { Address } from '@zoltar/shared/ethereum' import { loadReportingDetails, reportOutcomeInSecurityPool, withdrawEscalationFromSecurityPool } from '../../../protocol/index.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { normalizeAddress } from '../../../lib/address.js' -import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' -import { getErrorMessage } from '../../../lib/errors.js' -import { parseAddressInput } from '../../../lib/inputs.js' -import { getDefaultReportingFormState, getDefaultReportingWithdrawDepositIndexesByOutcome, parseRepAmountInput } from '../../markets/lib/marketForm.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { normalizeAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' +import { parseAddressInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { getDefaultReportingFormState, getDefaultReportingWithdrawDepositIndexesByOutcome } from '../../../lib/formDefaults.js' +import { parseRepAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' import { getRemainingSelectedOutcomeContributionCapacity, previewReportingContribution } from '../lib/reportingDomain.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createReportingSuccessPresentation, createReportingTransactionIntent, createReportingWarningPresentation } from '../../transactionPresentations.js' -import { buildWriteActionConfig, runWriteAction } from '../../../lib/writeAction.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { buildWriteActionConfig, runWriteAction } from '@zoltar/ui-core-shared/lib/writeAction.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { ReportingFormState, ReportingWithdrawDepositIndexesByOutcome, WriteOperationsParameters } from '../../../types/app.js' -import type { ReportingActionResult, ReportingDetails, ReportingOutcomeKey } from '../../../types/contracts.js' +import type { ReportingActionResult, ReportingDetails, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' type UseReportingOperationsParameters = WriteOperationsParameters type ResolvedReportingOperationsParameters = UseReportingOperationsParameters & { diff --git a/ui/zoltar/ts/features/reporting/lib/reporting.ts b/ui/zoltar/ts/features/reporting/lib/reporting.ts new file mode 100644 index 000000000..222d29cb2 --- /dev/null +++ b/ui/zoltar/ts/features/reporting/lib/reporting.ts @@ -0,0 +1,72 @@ +import type { ReportingDetails, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' +import { getEscalationPhase, isPoolQuestionFinalized } from './reportingDomain.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import { formatTimestampWithRelative } from '@zoltar/ui-core-shared/lib/formatters.js' + +const REPORTING_OUTCOME_OPTIONS: { key: ReportingOutcomeKey; label: string }[] = [ + { key: 'invalid', label: 'Invalid' }, + { key: 'yes', label: 'Yes' }, + { key: 'no', label: 'No' }, +] + +export const REPORTING_OUTCOME_DROPDOWN_OPTIONS = REPORTING_OUTCOME_OPTIONS.map(option => ({ + value: option.key, + label: option.label, +})) + +export function getReportingOutcomeLabel(outcome: ReportingOutcomeKey | 'none') { + switch (outcome) { + case 'invalid': + return 'Invalid' + case 'yes': + return 'Yes' + case 'no': + return 'No' + case 'none': + return 'Unresolved' + default: + return assertNever(outcome) + } +} + +export function getReportingLockedUntilMessage(endTime: bigint, currentTimestamp: bigint | undefined) { + return `Reporting opens when this pool's underlying question ends: ${formatTimestampWithRelative(endTime, currentTimestamp)}.` +} + +export function hasReportingOpened(endTime: bigint, currentTimestamp: bigint | undefined) { + if (currentTimestamp === undefined) return undefined + return currentTimestamp > endTime +} + +export type ReportingStage = 'preOpen' | 'notStarted' | 'activeLocked' | 'activeWithdrawable' | 'resolved' | 'forkTriggered' | 'timedOut' + +export function deriveReportingStage({ reportingDetails, reportingReady }: { reportingDetails: ReportingDetails | undefined; reportingReady: boolean | undefined }): ReportingStage | undefined { + if (reportingReady === false) return 'preOpen' + if (reportingDetails === undefined) return undefined + if (isPoolQuestionFinalized(reportingDetails)) return 'resolved' + if (reportingDetails.status === 'not-started') return 'notStarted' + + const escalationPhase = getEscalationPhase(reportingDetails) + switch (escalationPhase) { + case 'Resolved': + return 'resolved' + case 'Fork Triggered': + return 'forkTriggered' + case 'Timed Out': + return 'timedOut' + case 'Pending Start': + case 'Active': + if (reportingDetails.settlementState === 'migration-required' || reportingDetails.settlementState === 'migration-expired') return 'forkTriggered' + return reportingDetails.parentWithdrawalEnabled ? 'activeWithdrawable' : 'activeLocked' + default: + return assertNever(escalationPhase) + } +} + +export function isReportingOutcomeEnabled(stage: ReportingStage | undefined) { + return stage === 'notStarted' || stage === 'activeLocked' || stage === 'activeWithdrawable' +} + +export function isWithdrawEscalationEnabled(stage: ReportingStage | undefined) { + return stage === 'activeWithdrawable' || stage === 'resolved' +} diff --git a/ui/ts/features/reporting/lib/reportingDomain.ts b/ui/zoltar/ts/features/reporting/lib/reportingDomain.ts similarity index 98% rename from ui/ts/features/reporting/lib/reportingDomain.ts rename to ui/zoltar/ts/features/reporting/lib/reportingDomain.ts index 3633ffee8..f4d3fcfa3 100644 --- a/ui/ts/features/reporting/lib/reportingDomain.ts +++ b/ui/zoltar/ts/features/reporting/lib/reportingDomain.ts @@ -6,10 +6,10 @@ import { projectEscalationDeposit, type EscalationBalanceTuple, } from '@zoltar/shared/escalationMath' -import type { ActiveReportingDetails, EscalationDeposit, EscalationSide, ImportedEscalationDeposit, ReportingDetails, ReportingOutcomeKey } from '../../../types/contracts.js' -import { formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' -import { requireDefined } from '../../../lib/required.js' -import { getTimeRemaining } from '../../../lib/time.js' +import type { ActiveReportingDetails, EscalationDeposit, EscalationSide, ImportedEscalationDeposit, ReportingDetails, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' +import { formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { requireDefined } from '@zoltar/ui-core-shared/lib/required.js' +import { getTimeRemaining } from '@zoltar/ui-core-shared/lib/time.js' type ReportingAmountSuggestion = { amountAttoRep: bigint | undefined reason: string | undefined diff --git a/ui/ts/features/reporting/lib/reportingForm.ts b/ui/zoltar/ts/features/reporting/lib/reportingForm.ts similarity index 90% rename from ui/ts/features/reporting/lib/reportingForm.ts rename to ui/zoltar/ts/features/reporting/lib/reportingForm.ts index b23b9d54c..929cf115d 100644 --- a/ui/ts/features/reporting/lib/reportingForm.ts +++ b/ui/zoltar/ts/features/reporting/lib/reportingForm.ts @@ -1,5 +1,5 @@ -import { sameCaseInsensitiveText } from '../../../lib/caseInsensitive.js' -import { getDefaultReportingWithdrawDepositIndexesByOutcome } from '../../markets/lib/marketForm.js' +import { sameCaseInsensitiveText } from '@zoltar/ui-core-shared/lib/caseInsensitive.js' +import { getDefaultReportingWithdrawDepositIndexesByOutcome } from '../../../lib/formDefaults.js' import type { ReportingFormState } from '../../../types/app.js' export function applyReportingFormUpdate(current: ReportingFormState, update: Partial): ReportingFormState { diff --git a/ui/ts/features/reporting/lib/reportingGuards.ts b/ui/zoltar/ts/features/reporting/lib/reportingGuards.ts similarity index 93% rename from ui/ts/features/reporting/lib/reportingGuards.ts rename to ui/zoltar/ts/features/reporting/lib/reportingGuards.ts index d4a3fab07..c9281bf3b 100644 --- a/ui/ts/features/reporting/lib/reportingGuards.ts +++ b/ui/zoltar/ts/features/reporting/lib/reportingGuards.ts @@ -1,7 +1,7 @@ import type { Address } from '@zoltar/shared/ethereum' -import type { ReportingOutcomeKey } from '../../../types/contracts.js' -import { getWalletActiveAppChainGuardState } from '../../../lib/actionGuards.js' -import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '../../../lib/formatters.js' +import type { ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWalletActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' +import { formatAdditionalCurrencyBalance, formatCurrencyBalanceWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' type ReportingStatus = 'missing' | 'not-started' | 'active' diff --git a/ui/zoltar/ts/features/transactionPresentations.tsx b/ui/zoltar/ts/features/transactionPresentations.tsx new file mode 100644 index 000000000..dfd538fd6 --- /dev/null +++ b/ui/zoltar/ts/features/transactionPresentations.tsx @@ -0,0 +1,312 @@ +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as transactionCopy from '@zoltar/ui-core-shared/copy/transaction.js' +import * as marketCopy from '../copy/market.js' +import * as openOracleCopy from '../copy/openOracle.js' +import * as securityPoolCopy from '../copy/securityPool.js' +import type { Hash } from '@zoltar/shared/ethereum' +import { AddressValue } from '@zoltar/ui-core-shared/components/AddressValue.js' +import { IdentifierValue } from '@zoltar/ui-core-shared/components/IdentifierValue.js' +import { formatCurrencyBalanceWithUnit, formatValueWithUnit } from '@zoltar/ui-core-shared/lib/formatters.js' +import { getReportingOutcomeLabel } from './reporting/lib/reporting.js' +import { getMarketTypeLabel } from '@zoltar/ui-core-shared/lib/marketType.js' +import { buildIntent, buildPresentation, getPoolUniverseTransactionRows, humanizeTransactionAction, withWarning } from '@zoltar/ui-core-shared/lib/transactionPresentations.js' +import type { PoolUniverseTransactionContext } from '@zoltar/ui-core-shared/lib/transactionPresentations.js' +import type { MarketCreationResult, OpenOracleActionResult, ReportingActionResult, ZoltarChildUniverseActionResult, ZoltarForkActionResult, ZoltarMigrationActionResult } from '@zoltar/ui-core-shared/types/contracts.js' +export function createDeploymentTransactionIntent(stepLabel: string) { + return buildIntent({ + action: 'deploy', + source: 'deployment', + submittedTitle: transactionCopy.formatDeployingValue(stepLabel), + }) +} + +export function createDeploymentSuccessPresentation(stepLabel: string, hash: Hash) { + return buildPresentation({ + hash, + title: transactionCopy.formatValueDeployed(stepLabel), + tone: 'success', + }) +} + +type MarketCreationTransactionContext = { + marketType: MarketCreationResult['marketType'] + title?: string | undefined + universeId?: bigint | undefined +} + +function getMarketCreationTransactionRows(context: MarketCreationTransactionContext) { + return [...(context.title === undefined || context.title.trim() === '' ? [] : [{ label: marketCopy.title, value: context.title.trim() }]), { label: marketCopy.questionType, value: getMarketTypeLabel(context.marketType) }] +} + +export function createMarketCreationTransactionIntent(context: MarketCreationTransactionContext) { + return buildIntent({ + action: 'createMarket', + rows: getMarketCreationTransactionRows(context), + source: 'zoltar', + submittedTitle: transactionCopy.creatingQuestion, + universeId: context.universeId, + }) +} + +export function createMarketCreationSuccessPresentation(result: MarketCreationResult, context?: Omit) { + return buildPresentation({ + hash: result.createQuestionHash, + rows: [{ label: commonCopy.questionId, value: }, ...getMarketCreationTransactionRows({ ...context, marketType: result.marketType })], + title: transactionCopy.questionCreated, + tone: 'success', + universeId: context?.universeId, + }) +} + +export function createMarketCreationWarningPresentation(result: MarketCreationResult, message: string, context?: Omit) { + return withWarning(createMarketCreationSuccessPresentation(result, context), message) +} + +type QuestionUniverseTransactionContext = { + questionId?: string | undefined + universeId?: bigint | undefined +} + +function getQuestionUniverseTransactionRows(context: QuestionUniverseTransactionContext | undefined) { + if (context === undefined) return undefined + return [...(context.questionId === undefined || context.questionId.trim() === '' ? [] : [{ label: commonCopy.questionId, value: }])] +} + +export function createZoltarForkTransactionIntent(actionName: 'approve' | 'fork', context?: QuestionUniverseTransactionContext) { + return buildIntent({ + action: actionName, + rows: getQuestionUniverseTransactionRows(context), + source: 'zoltar', + submittedTitle: actionName === 'approve' ? transactionCopy.approvingForkRep : transactionCopy.forkingZoltar, + universeId: context?.universeId, + }) +} + +export function createZoltarForkSuccessPresentation(result: ZoltarForkActionResult) { + const title = result.action === 'approveForkRep' ? transactionCopy.forkRepApproved : transactionCopy.zoltarForkSubmitted + return buildPresentation({ + hash: result.hash, + rows: [{ label: commonCopy.questionId, value: }], + title, + tone: 'success', + universeId: result.universeId, + }) +} + +export function createZoltarForkWarningPresentation(result: ZoltarForkActionResult, message: string) { + return withWarning(createZoltarForkSuccessPresentation(result), message) +} + +type ChildUniverseTransactionContext = { + outcomeIndex?: bigint | undefined + universeId?: bigint | undefined +} + +function getChildUniverseTransactionRows(context: ChildUniverseTransactionContext | undefined) { + if (context === undefined) return undefined + return context.outcomeIndex === undefined ? [] : [{ label: commonCopy.outcomeIndex, value: context.outcomeIndex.toString() }] +} + +export function createChildUniverseTransactionIntent(source: 'fork-auction' | 'zoltar', context?: ChildUniverseTransactionContext) { + return buildIntent({ + action: 'createChildUniverse', + rows: getChildUniverseTransactionRows(context), + source, + submittedTitle: transactionCopy.deployingChildUniverse, + universeId: context?.universeId, + }) +} + +export function createChildUniverseSuccessPresentation(result: ZoltarChildUniverseActionResult) { + return buildPresentation({ + hash: result.hash, + rows: [{ label: commonCopy.outcomeIndex, value: result.outcomeIndex.toString() }], + title: transactionCopy.childUniverseDeployed, + tone: 'success', + universeId: result.universeId, + }) +} + +export function createChildUniverseWarningPresentation(result: ZoltarChildUniverseActionResult, message: string) { + return withWarning(createChildUniverseSuccessPresentation(result), message) +} + +type ZoltarMigrationTransactionContext = { + amount?: string | undefined + outcomeIndexes?: string | undefined + universeId?: bigint | undefined +} + +function getZoltarMigrationTransactionRows(context: ZoltarMigrationTransactionContext | undefined) { + if (context === undefined) return undefined + return [ + ...(context.amount === undefined || context.amount.trim() === '' ? [] : [{ label: commonCopy.amount, value: formatValueWithUnit(context.amount.trim(), commonCopy.rep) }]), + ...(context.outcomeIndexes === undefined || context.outcomeIndexes.trim() === '' ? [] : [{ label: transactionCopy.outcomeIndexes, value: context.outcomeIndexes.trim() }]), + ] +} + +export function createZoltarMigrationTransactionIntent(actionName: 'prepare' | 'split', context?: ZoltarMigrationTransactionContext) { + return buildIntent({ + action: actionName, + rows: getZoltarMigrationTransactionRows(context), + source: 'zoltar', + submittedTitle: actionName === 'prepare' ? transactionCopy.preparingRep : transactionCopy.splittingRep, + universeId: context?.universeId, + }) +} + +export function createZoltarMigrationSuccessPresentation(result: ZoltarMigrationActionResult) { + return buildPresentation({ + detail: result.action === 'addRepToMigrationBalance' ? transactionCopy.migrationRepPreparationSuccessDetail : transactionCopy.repSplitSuccessDetail, + hash: result.hash, + rows: [ + { label: commonCopy.amount, value: formatCurrencyBalanceWithUnit(result.amountAttoRep, commonCopy.rep) }, + { label: transactionCopy.outcomeIndexes, value: result.outcomeIndexes.length === 0 ? commonCopy.none : result.outcomeIndexes.join(', ') }, + ], + title: result.action === 'addRepToMigrationBalance' ? transactionCopy.repPrepared : transactionCopy.repSplit, + tone: 'success', + universeId: result.universeId, + }) +} + +export function createZoltarMigrationWarningPresentation(result: ZoltarMigrationActionResult, message: string) { + return withWarning(createZoltarMigrationSuccessPresentation(result), message) +} + +type ReportingTransactionContext = PoolUniverseTransactionContext & { + outcome?: ReportingActionResult['outcome'] | undefined +} + +function getReportingTransactionRows(context: ReportingTransactionContext | undefined) { + return [...(getPoolUniverseTransactionRows(context) ?? []), ...(context?.outcome === undefined ? [] : [{ label: commonCopy.outcome, value: getReportingOutcomeLabel(context.outcome) }])] +} + +export function createReportingTransactionIntent(actionName: ReportingActionResult['action'], context?: ReportingTransactionContext) { + return buildIntent({ + action: actionName, + rows: getReportingTransactionRows(context), + source: 'reporting', + submittedTitle: humanizeTransactionAction(actionName), + universeId: context?.universeId, + }) +} + +export function createReportingSuccessPresentation(result: ReportingActionResult) { + const detail = result.action === 'reportOutcome' ? transactionCopy.reportingContributionSuccessDetail : transactionCopy.escalationDepositsSettledDetail + return buildPresentation({ + detail, + hash: result.hash, + rows: [ + { label: transactionCopy.pool, value: }, + { label: commonCopy.outcome, value: getReportingOutcomeLabel(result.outcome) }, + ], + title: humanizeTransactionAction(result.action), + tone: 'success', + universeId: result.universeId, + }) +} + +export function createReportingWarningPresentation(result: ReportingActionResult, message: string) { + return withWarning(createReportingSuccessPresentation(result), message) +} + +type PoolOracleTransactionContext = { + managerAddress: string + securityPoolAddress?: string | undefined + universeId?: bigint | undefined +} + +function getPoolOracleTransactionRows(context: PoolOracleTransactionContext | undefined) { + if (context === undefined) return undefined + return [...(context.securityPoolAddress === undefined ? [] : [{ label: commonCopy.securityPoolAddress, value: }]), { label: securityPoolCopy.oracleManager, value: }] +} + +export function createPoolOracleTransactionIntent(actionName: 'executeStagedOperation' | 'requestPrice', context?: PoolOracleTransactionContext) { + let submittedTitle: string = transactionCopy.executingStagedOperation + if (actionName === 'requestPrice') { + submittedTitle = transactionCopy.requestingPrice + } + return buildIntent({ + action: actionName, + rows: getPoolOracleTransactionRows(context), + source: 'pool-oracle', + submittedTitle, + universeId: context?.universeId, + }) +} + +export function createPoolOracleSuccessPresentation(result: OpenOracleActionResult, context?: PoolOracleTransactionContext) { + let title: string = transactionCopy.stagedOperationExecuted + if (result.action === 'requestPrice') { + title = transactionCopy.priceRequested + } + return buildPresentation({ + hash: result.hash, + rows: getPoolOracleTransactionRows(context), + title, + tone: 'success', + universeId: context?.universeId, + }) +} + +export function createPoolOracleWarningPresentation(result: OpenOracleActionResult, message: string, context?: PoolOracleTransactionContext) { + return withWarning(createPoolOracleSuccessPresentation(result, context), message) +} + +type OpenOracleTransactionContext = { + openOracleAddress?: string | undefined + reportId?: string | undefined + token1Symbol?: string | undefined + token2Symbol?: string | undefined + tokenPair?: string | undefined + withdrawalTokenSymbol?: string | undefined +} + +function getOpenOracleTransactionRows(context: OpenOracleTransactionContext | undefined) { + if (context === undefined) return undefined + return [ + ...(context.reportId === undefined || context.reportId.trim() === '' ? [] : [{ label: openOracleCopy.reportId, value: context.reportId }]), + ...(context.tokenPair === undefined || context.tokenPair.trim() === '' ? [] : [{ label: openOracleCopy.tokenPair, value: context.tokenPair }]), + ...(context.openOracleAddress === undefined ? [] : [{ label: openOracleCopy.oracleAddress, value: }]), + ] +} + +function getOpenOracleSubmittedTitle(actionName: OpenOracleActionResult['action'], context: OpenOracleTransactionContext | undefined) { + if (actionName === 'approveToken1') return openOracleCopy.formatApproveToken(context?.token1Symbol ?? openOracleCopy.baseToken) + if (actionName === 'approveToken2') return openOracleCopy.formatApproveToken(context?.token2Symbol ?? openOracleCopy.quoteToken) + if (actionName === 'createReportInstance') return openOracleCopy.createReport + if (actionName === 'settle') return openOracleCopy.settlingReportTitle + if (actionName === 'withdrawBalance') return openOracleCopy.withdrawBalance(context?.withdrawalTokenSymbol ?? openOracleCopy.oracleBalance) + return humanizeTransactionAction(actionName) +} + +function getOpenOracleSuccessTitle(actionName: OpenOracleActionResult['action'], context: OpenOracleTransactionContext | undefined) { + if (actionName === 'approveToken1') return openOracleCopy.formatTokenApproved(context?.token1Symbol ?? openOracleCopy.baseToken) + if (actionName === 'approveToken2') return openOracleCopy.formatTokenApproved(context?.token2Symbol ?? openOracleCopy.quoteToken) + if (actionName === 'createReportInstance') return openOracleCopy.reportCreated + if (actionName === 'settle') return openOracleCopy.reportSettled + if (actionName === 'withdrawBalance') return openOracleCopy.formatTokenWithdrawn(context?.withdrawalTokenSymbol ?? openOracleCopy.oracleBalance) + return humanizeTransactionAction(actionName) +} + +export function createOpenOracleTransactionIntent(actionName: OpenOracleActionResult['action'], context?: OpenOracleTransactionContext) { + return buildIntent({ + action: actionName, + rows: getOpenOracleTransactionRows(context), + source: 'open-oracle', + submittedTitle: getOpenOracleSubmittedTitle(actionName, context), + }) +} + +export function createOpenOracleSuccessPresentation(result: OpenOracleActionResult, context?: OpenOracleTransactionContext) { + return buildPresentation({ + hash: result.hash, + rows: getOpenOracleTransactionRows(context), + title: getOpenOracleSuccessTitle(result.action, context), + tone: 'success', + }) +} + +export function createOpenOracleWarningPresentation(result: OpenOracleActionResult, message: string, context?: OpenOracleTransactionContext) { + return withWarning(createOpenOracleSuccessPresentation(result, context), message) +} diff --git a/ui/zoltar/ts/features/types.ts b/ui/zoltar/ts/features/types.ts new file mode 100644 index 000000000..b49764c2c --- /dev/null +++ b/ui/zoltar/ts/features/types.ts @@ -0,0 +1,225 @@ +import type { Address } from '@zoltar/shared/ethereum' +import type { AccountState, OpenOracleCreateFormState, OpenOracleFormState, ReportingFormState, ZoltarMigrationFormState } from '../types/app.js' +import type { + DeploymentStatus, + DeploymentStepId, + MarketDetails, + MarketDetailsPage, + OpenOracleActionResult, + OpenOracleReportDetails, + OpenOracleReportSummaryPage, + OpenOracleWithdrawableBalances, + ReportingActionResult, + ReportingDetails, + ReportingOutcomeKey, + ZoltarUniverseSummary, +} from '@zoltar/ui-core-shared/types/contracts.js' +import type { ActionAvailability } from '@zoltar/ui-core-shared/types/components.js' +import type { OpenOracleCreateContractFieldErrors, OpenOracleDisputeSubmissionDetails } from './open-oracle/lib/openOracle.js' +import type { LoadableValueState } from '@zoltar/ui-core-shared/lib/loadState.js' +import type { TokenApprovalState } from '@zoltar/ui-core-shared/lib/tokenApproval.js' +import type { UserMessagePresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import type { ReadBackendStatus } from '@zoltar/ui-core-shared/lib/chainBackend.js' + +export type * from '@zoltar/ui-core-shared/types/components.js' + +type RepPerEthPriceProps = { + repPerEthPrice: bigint | undefined + repPerEthSource: 'v4' | 'v3' | 'mock' | undefined + repPerEthSourceUrl: string | undefined +} + +export type RepPriceFailure = 'no-liquidity' | 'rpc-error' + +export type DeploymentSectionProps = { + title: string + completedGroup?: boolean + steps: DeploymentStatus[] + allSteps: DeploymentStatus[] + accountAddress: Address | undefined + isOnActiveAppChain: boolean + busyStepId: DeploymentStepId | undefined + deploymentStateReady: boolean + deploymentStatusReasonElementId?: string | undefined + onDeploy: (stepId: DeploymentStepId) => Promise +} + +export type OverviewPanelsProps = { + applicationTitle: string + activeUniverseId: bigint + accountState: AccountState + isConnectingWallet: boolean + isManagingWallet: boolean + walletBootstrapComplete: boolean + parentUniverseId: bigint | undefined + universeRepBalanceAttoRep: bigint | undefined + isLoadingUniverseRepBalance: boolean + universeForkTime?: bigint | undefined + universeHasForked?: boolean | undefined + universePresentation: UserMessagePresentation | undefined + universeLabel: string + isRefreshing: boolean + repUsdcPrice: bigint | undefined + repUsdcFailure: RepPriceFailure | undefined + repUsdcSource: 'v4' | 'v3' | 'mock' | undefined + repUsdcSourceUrl: string | undefined + isLoadingRepPrices: boolean + isRefreshingRepPrices: boolean + onConnect: () => void + onChangeWallet: () => void + onDisconnectWallet: () => void + onGoToGenesisUniverse: () => void + onRefreshRepPrices: () => void + onSwitchNetwork: () => void + readBackendStatus?: ReadBackendStatus + repPerEthFailure: RepPriceFailure | undefined +} & RepPerEthPriceProps + +export type ZoltarView = 'create' | 'fork' | 'migrate' | 'questions' + +export type DeploymentRouteContentProps = { + accountAddress: Address | undefined + busyStepId: DeploymentStepId | undefined + deploymentStateReady: boolean + deploymentStatusError: string | undefined + deploymentSections: { title: string; steps: DeploymentStatus[] }[] + deploymentStatuses: DeploymentStatus[] + isLoadingDeploymentStatuses: boolean + isOnActiveAppChain: boolean + deployNextMissingPending: boolean + deploymentCompleteHref?: string + onDeploy: (stepId: DeploymentStepId) => Promise + onDeployNextMissing: () => void + onRetryDeploymentStatus: () => void +} + +export type MarketRouteContentProps = { + accountState: AccountState + activeUniverseId: bigint + activeView: ZoltarView + environmentRefreshKey: number + onApproveZoltarForkRep: (amount?: bigint) => void + onCreateChildUniverseForOutcomeIndex: (outcomeIndex: bigint) => void + onForkZoltar: () => void + onMigrateInternalRep: () => void + onPrepareRepForMigration: () => void + onActiveViewChange: (view: ZoltarView) => void + loadingZoltarQuestionCount: boolean + loadingZoltarQuestion: boolean + loadingZoltarQuestions: boolean + hasLoadedZoltarQuestions: boolean + zoltarForkActiveAction: 'approve' | 'fork' | undefined + loadingZoltarUniverse: boolean + zoltarUniverseState: LoadableValueState + onLoadZoltarQuestions: () => Promise + onLoadZoltarQuestion: (questionId: string) => Promise + onLoadZoltarQuestionPage: (pageIndex: number, pageSize: number) => Promise + onZoltarMigrationFormChange: (update: Partial) => void + zoltarQuestionCount: bigint | undefined + zoltarQuestionLookupError: string | undefined + zoltarQuestionLookupId: string | undefined + zoltarQuestionPage: MarketDetailsPage | undefined + zoltarForkApproval: TokenApprovalState + zoltarForkError: string | undefined + loadingZoltarForkAccess: boolean + zoltarChildUniverseError: string | undefined + zoltarChildUniversePendingOutcomeIndex: bigint | undefined + zoltarForkPending: boolean + zoltarForkQuestionId: string + zoltarForkRepBalanceAttoRep: bigint | undefined + zoltarMigrationError: string | undefined + zoltarMigrationForm: ZoltarMigrationFormState + zoltarMigrationChildRepBalancesAttoRep: Record + zoltarMigrationPending: boolean + zoltarMigrationPreparedRepBalanceAttoRep: bigint | undefined + zoltarQuestions: MarketDetails[] + zoltarQuestionsError: string | undefined + zoltarMigrationActiveAction: 'prepare' | 'split' | undefined + zoltarUniverse: ZoltarUniverseSummary | undefined + onZoltarForkQuestionIdChange: (questionId: string) => void +} + +export type OpenOracleReportLookupState = 'unknown' | 'loading' | 'ready' | 'missing' | 'load-failed' + +export type OpenOracleView = 'browse' | 'create' | 'selected-report' + +export type OpenOracleSectionProps = OpenOracleRouteContentProps & { + activeView: OpenOracleView + environmentReady: boolean + environmentRefreshKey: number + loadBrowseReports?: (pageIndex: number, pageSize: number) => Promise + onActiveViewChange: (view: OpenOracleView) => void +} + +export type ReportingRouteContentProps = { + accountState: AccountState + loadingReportingDetails: boolean + onLoadReporting: () => void + onReportOutcome: () => void + onReportingFormChange: (update: Partial) => void + onWithdrawEscalation: (outcome: ReportingOutcomeKey, depositIndexes?: bigint[]) => void + reportingActiveAction: ReportingActionResult['action'] | undefined + reportingDetails: ReportingDetails | undefined + reportingError: string | undefined + reportingForm: ReportingFormState + reportingResult: ReportingActionResult | undefined +} + +export type ReportingSectionProps = ReportingRouteContentProps & { + currentTimestamp?: bigint | undefined + embedInCard?: boolean + forkAlreadyTriggered?: boolean | undefined + lockedReason?: string | undefined + mode?: 'full-reporting' | 'withdraw-only' + onOpenForkWorkflow?: (() => void) | undefined + onOpenPriceOracle?: (() => void) | undefined + onTriggerZoltarFork?: (() => void) | undefined + previewMarketDetails?: MarketDetails | undefined + reportActionGuardMessage?: string | undefined + showHeader?: boolean + showSecurityPoolAddressInput?: boolean + triggerZoltarForkAvailability?: ActionAvailability | undefined + triggerZoltarForkPending?: boolean | undefined +} + +type OpenOracleRouteContentProps = { + accountState: AccountState + onApproveToken1: (amount?: bigint) => void + onApproveToken2: (amount?: bigint) => void + onCancelOpenOracleWithdrawalBalanceCheck: () => void + onCreateOpenOracleGame: () => void + onDisputeReport: () => void + onLoadOracleReport: (reportId?: string) => void + onOpenOracleFormChange: (update: Partial) => void + onOpenOracleCreateFormChange: (update: Partial) => void + onSettleReport: () => void + onWithdrawOpenOracleBalance: (balance: keyof OpenOracleWithdrawableBalances, reviewedAmount: bigint) => void + loadingOpenOracleCreate: boolean + openOracleActiveAction: OpenOracleActionResult['action'] | undefined + openOracleActiveWithdrawalBalance: keyof OpenOracleWithdrawableBalances | undefined + openOracleError: string | undefined + openOracleTokenAccessState: { + token1Approval: TokenApprovalState + token1Balance: bigint | undefined + token1BalanceError: string | undefined + token1Decimals: number | undefined + token2Approval: TokenApprovalState + token2Balance: bigint | undefined + token2BalanceError: string | undefined + token2Decimals: number | undefined + tokenAccessLoadingInitial: boolean + tokenAccessRefreshing: boolean + } + openOracleDisputeSubmission: OpenOracleDisputeSubmissionDetails | undefined + openOracleCreateForm: OpenOracleCreateFormState + openOracleCreateFieldErrors?: OpenOracleCreateContractFieldErrors + openOracleForm: OpenOracleFormState + openOracleReportLookupState: OpenOracleReportLookupState + openOracleReportDetails: OpenOracleReportDetails | undefined + openOracleResult: OpenOracleActionResult | undefined + openOracleWithdrawalBalanceChecking: boolean + openOracleWithdrawalReviewMessage: { balance: keyof OpenOracleWithdrawableBalances; message: string } | undefined + openOracleWithdrawableBalances: OpenOracleWithdrawableBalances | undefined + openOracleWithdrawableBalancesError: string | undefined + openOracleWithdrawableBalancesLoading: boolean +} diff --git a/ui/ts/features/universes/components/ChildUniverseDeploymentModal.tsx b/ui/zoltar/ts/features/universes/components/ChildUniverseDeploymentModal.tsx similarity index 79% rename from ui/ts/features/universes/components/ChildUniverseDeploymentModal.tsx rename to ui/zoltar/ts/features/universes/components/ChildUniverseDeploymentModal.tsx index 86c060759..84482dfa6 100644 --- a/ui/ts/features/universes/components/ChildUniverseDeploymentModal.tsx +++ b/ui/zoltar/ts/features/universes/components/ChildUniverseDeploymentModal.tsx @@ -1,7 +1,7 @@ import type { ComponentChildren } from 'preact' -import { OperationModal } from '../../../components/OperationModal.js' -import { RequirementsChecklist } from '../../../components/RequirementsChecklist.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' +import { OperationModal } from '@zoltar/ui-core-shared/components/OperationModal.js' +import { RequirementsChecklist } from '@zoltar/ui-core-shared/components/RequirementsChecklist.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' import type { ActionAvailability, ReadinessBlocker } from '../../types.js' type ChildUniverseDeploymentModalProps = { diff --git a/ui/ts/features/universes/components/ChildUniverseDetails.tsx b/ui/zoltar/ts/features/universes/components/ChildUniverseDetails.tsx similarity index 69% rename from ui/ts/features/universes/components/ChildUniverseDetails.tsx rename to ui/zoltar/ts/features/universes/components/ChildUniverseDetails.tsx index ee384b39b..6c2865e52 100644 --- a/ui/ts/features/universes/components/ChildUniverseDetails.tsx +++ b/ui/zoltar/ts/features/universes/components/ChildUniverseDetails.tsx @@ -1,10 +1,10 @@ -import * as commonCopy from '../../../copy/common.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { TimestampValue } from '../../../components/TimestampValue.js' -import { MetricField } from '../../../components/MetricField.js' -import { WalletAssetControl } from '../../../components/WalletAssetControl.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { TimestampValue } from '@zoltar/ui-core-shared/components/TimestampValue.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { WalletAssetControl } from '@zoltar/ui-core-shared/components/WalletAssetControl.js' import type { Address } from '@zoltar/shared/ethereum' -import type { ZoltarChildUniverseSummary } from '../../../types/contracts.js' +import type { ZoltarChildUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type ChildUniverseDetailsProps = { accountAddress: Address | undefined diff --git a/ui/ts/features/universes/components/ChildUniversesSection.tsx b/ui/zoltar/ts/features/universes/components/ChildUniversesSection.tsx similarity index 83% rename from ui/ts/features/universes/components/ChildUniversesSection.tsx rename to ui/zoltar/ts/features/universes/components/ChildUniversesSection.tsx index ed082d2a5..8d333085e 100644 --- a/ui/ts/features/universes/components/ChildUniversesSection.tsx +++ b/ui/zoltar/ts/features/universes/components/ChildUniversesSection.tsx @@ -1,11 +1,11 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import type { ComponentChildren } from 'preact' -import { Badge } from '../../../components/Badge.js' -import { EntityCard } from '../../../components/EntityCard.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { WorkflowSubsection } from '../../../components/WorkflowSubsection.js' +import { Badge } from '@zoltar/ui-core-shared/components/Badge.js' +import { EntityCard } from '@zoltar/ui-core-shared/components/EntityCard.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { WorkflowSubsection } from '@zoltar/ui-core-shared/components/WorkflowSubsection.js' import type { ActionAvailability } from '../../types.js' -import type { ZoltarChildUniverseSummary } from '../../../types/contracts.js' +import type { ZoltarChildUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type ChildUniverseAction = { availability?: ActionAvailability diff --git a/ui/ts/features/universes/components/ForkZoltarSection.tsx b/ui/zoltar/ts/features/universes/components/ForkZoltarSection.tsx similarity index 87% rename from ui/ts/features/universes/components/ForkZoltarSection.tsx rename to ui/zoltar/ts/features/universes/components/ForkZoltarSection.tsx index 64bd84391..1273262aa 100644 --- a/ui/ts/features/universes/components/ForkZoltarSection.tsx +++ b/ui/zoltar/ts/features/universes/components/ForkZoltarSection.tsx @@ -1,25 +1,25 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as zoltarCopy from '../../../copy/zoltar.js' import type { Address } from '@zoltar/shared/ethereum' import { useState } from 'preact/hooks' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { MetricField } from '../../../components/MetricField.js' -import { Question } from '../../markets/components/Question.js' -import { StateHint } from '../../../components/StateHint.js' -import { TokenApprovalControl } from '../../../components/TokenApprovalControl.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { WorkflowSubsection } from '../../../components/WorkflowSubsection.js' -import { normalizeQuestionId } from '../../../lib/questionId.js' -import { useChainTimestamp } from '../../../lib/chainTimestamp.js' -import { formatRelativeTimestamp, formatTimestamp } from '../../../lib/formatters.js' -import { resolveLoadableValueState, type LoadableValueState } from '../../../lib/loadState.js' -import { deriveTokenApprovalRequirement, type TokenApprovalState } from '../../../lib/tokenApproval.js' -import { getReportPresentation, getUniversePresentation, getWalletPresentation } from '../../../lib/userCopy.js' -import type { MarketDetails, ZoltarUniverseSummary } from '../../../types/contracts.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { Question } from '@zoltar/ui-core-shared/components/Question.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { TokenApprovalControl } from '@zoltar/ui-core-shared/components/TokenApprovalControl.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { WorkflowSubsection } from '@zoltar/ui-core-shared/components/WorkflowSubsection.js' +import { normalizeQuestionId } from '@zoltar/ui-core-shared/lib/questionId.js' +import { useChainTimestamp } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { formatRelativeTimestamp, formatTimestamp } from '@zoltar/ui-core-shared/lib/formatters.js' +import { resolveLoadableValueState, type LoadableValueState } from '@zoltar/ui-core-shared/lib/loadState.js' +import { deriveTokenApprovalRequirement, type TokenApprovalState } from '@zoltar/ui-core-shared/lib/tokenApproval.js' +import { getReportPresentation, getUniversePresentation, getWalletPresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import type { MarketDetails, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' const FORK_CONFIRMATION = 'FORK' const FORK_QUESTION_ERROR_ID = 'fork-zoltar-question-error' @@ -35,7 +35,7 @@ type ForkZoltarSectionProps = { loadingZoltarQuestions: boolean onApproveZoltarForkRep: (amount?: bigint) => void onForkZoltar: () => void - onRetryZoltarQuestion?: () => void + onRetryZoltarQuestion?: (() => void) | undefined onZoltarForkQuestionIdChange: (questionId: string) => void zoltarForkActiveAction: 'approve' | 'fork' | undefined zoltarForkApproval: TokenApprovalState diff --git a/ui/ts/features/universes/components/MigrationOutcomeUniversesSection.tsx b/ui/zoltar/ts/features/universes/components/MigrationOutcomeUniversesSection.tsx similarity index 89% rename from ui/ts/features/universes/components/MigrationOutcomeUniversesSection.tsx rename to ui/zoltar/ts/features/universes/components/MigrationOutcomeUniversesSection.tsx index 5096aefc9..dbe9995d0 100644 --- a/ui/ts/features/universes/components/MigrationOutcomeUniversesSection.tsx +++ b/ui/zoltar/ts/features/universes/components/MigrationOutcomeUniversesSection.tsx @@ -1,9 +1,9 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as zoltarCopy from '../../../copy/zoltar.js' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { OutcomeSelectionList } from '../../markets/components/OutcomeSelectionList.js' -import { WorkflowSubsection } from '../../../components/WorkflowSubsection.js' -import type { ZoltarChildUniverseSummary } from '../../../types/contracts.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { OutcomeSelectionList } from '@zoltar/ui-core-shared/components/OutcomeSelectionList.js' +import { WorkflowSubsection } from '@zoltar/ui-core-shared/components/WorkflowSubsection.js' +import type { ZoltarChildUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type MigrationOutcomeUniversesSectionProps = { childUniverses: ZoltarChildUniverseSummary[] diff --git a/ui/ts/features/universes/components/ShareMigrationTargetsSection.tsx b/ui/zoltar/ts/features/universes/components/ShareMigrationTargetsSection.tsx similarity index 93% rename from ui/ts/features/universes/components/ShareMigrationTargetsSection.tsx rename to ui/zoltar/ts/features/universes/components/ShareMigrationTargetsSection.tsx index b645e1041..c9cd605f6 100644 --- a/ui/ts/features/universes/components/ShareMigrationTargetsSection.tsx +++ b/ui/zoltar/ts/features/universes/components/ShareMigrationTargetsSection.tsx @@ -1,13 +1,13 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as tradingCopy from '../../../copy/trading.js' import type { ComponentChildren } from 'preact' import { useEffect, useMemo, useState } from 'preact/hooks' -import { OutcomeSelectionList } from '../../markets/components/OutcomeSelectionList.js' -import { ScalarOutcomePicker } from '../../markets/components/ScalarOutcomePicker.js' -import { WorkflowSubsection } from '../../../components/WorkflowSubsection.js' -import { LoadingText } from '../../../components/LoadingText.js' -import { clampScalarTickIndex, formatScalarOutcomeIndexLabel, formatScalarOutcomeLabel, getScalarOutcomeIndex, getScalarOutcomeIndexDescriptor } from '../../markets/lib/scalarOutcome.js' -import type { MarketDetails, ZoltarChildUniverseSummary, ZoltarUniverseSummary } from '../../../types/contracts.js' +import { OutcomeSelectionList } from '@zoltar/ui-core-shared/components/OutcomeSelectionList.js' +import { ScalarOutcomePicker } from '@zoltar/ui-core-shared/components/ScalarOutcomePicker.js' +import { WorkflowSubsection } from '@zoltar/ui-core-shared/components/WorkflowSubsection.js' +import { LoadingText } from '@zoltar/ui-core-shared/components/LoadingText.js' +import { clampScalarTickIndex, formatScalarOutcomeIndexLabel, formatScalarOutcomeLabel, getScalarOutcomeIndex, getScalarOutcomeIndexDescriptor } from '@zoltar/ui-core-shared/lib/scalarOutcome.js' +import type { MarketDetails, ZoltarChildUniverseSummary, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type ShareMigrationTargetsSectionProps = { disabled: boolean diff --git a/ui/ts/features/universes/components/TransactionUniverseValue.tsx b/ui/zoltar/ts/features/universes/components/TransactionUniverseValue.tsx similarity index 82% rename from ui/ts/features/universes/components/TransactionUniverseValue.tsx rename to ui/zoltar/ts/features/universes/components/TransactionUniverseValue.tsx index 4a21e7191..e21e9eed9 100644 --- a/ui/ts/features/universes/components/TransactionUniverseValue.tsx +++ b/ui/zoltar/ts/features/universes/components/TransactionUniverseValue.tsx @@ -1,4 +1,4 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import { formatUniverseLabel } from '../lib/universe.js' type TransactionUniverseValueProps = { diff --git a/ui/ts/features/universes/components/UniverseLink.tsx b/ui/zoltar/ts/features/universes/components/UniverseLink.tsx similarity index 100% rename from ui/ts/features/universes/components/UniverseLink.tsx rename to ui/zoltar/ts/features/universes/components/UniverseLink.tsx diff --git a/ui/ts/features/universes/components/ZoltarMigrationSection.tsx b/ui/zoltar/ts/features/universes/components/ZoltarMigrationSection.tsx similarity index 90% rename from ui/ts/features/universes/components/ZoltarMigrationSection.tsx rename to ui/zoltar/ts/features/universes/components/ZoltarMigrationSection.tsx index 4574de582..3bb7ce99a 100644 --- a/ui/ts/features/universes/components/ZoltarMigrationSection.tsx +++ b/ui/zoltar/ts/features/universes/components/ZoltarMigrationSection.tsx @@ -1,33 +1,33 @@ -import * as commonCopy from '../../../copy/common.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as zoltarCopy from '../../../copy/zoltar.js' -import * as transactionReviewCopy from '../../../copy/transactionReview.js' +import * as transactionReviewCopy from '@zoltar/ui-core-shared/copy/transactionReview.js' import { useMemo } from 'preact/hooks' import type { Address } from '@zoltar/shared/ethereum' -import { CurrencyValue } from '../../../components/CurrencyValue.js' -import { DataGrid } from '../../../components/DataGrid.js' -import { ErrorNotice } from '../../../components/ErrorNotice.js' -import { FormInput } from '../../../components/FormInput.js' -import { MetricField } from '../../../components/MetricField.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { StateHint } from '../../../components/StateHint.js' -import { TokenApprovalControl } from '../../../components/TokenApprovalControl.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' -import { TransactionReview } from '../../../components/TransactionReview.js' -import { ReadOnlyDetailAccordion } from '../../../components/ReadOnlyDetailAccordion.js' -import { MetricGrid } from '../../../components/MetricGrid.js' -import { WorkflowSubsection } from '../../../components/WorkflowSubsection.js' -import { WalletAssetControl } from '../../../components/WalletAssetControl.js' +import { CurrencyValue } from '@zoltar/ui-core-shared/components/CurrencyValue.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { ErrorNotice } from '@zoltar/ui-core-shared/components/ErrorNotice.js' +import { FormInput } from '@zoltar/ui-core-shared/components/FormInput.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { TokenApprovalControl } from '@zoltar/ui-core-shared/components/TokenApprovalControl.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' +import { TransactionReview } from '@zoltar/ui-core-shared/components/TransactionReview.js' +import { ReadOnlyDetailAccordion } from '@zoltar/ui-core-shared/components/ReadOnlyDetailAccordion.js' +import { MetricGrid } from '@zoltar/ui-core-shared/components/MetricGrid.js' +import { WorkflowSubsection } from '@zoltar/ui-core-shared/components/WorkflowSubsection.js' +import { WalletAssetControl } from '@zoltar/ui-core-shared/components/WalletAssetControl.js' import { getMigrationOutcomeSplitLimit, MigrationOutcomeUniversesSection } from './MigrationOutcomeUniversesSection.js' -import type { LoadableValueState } from '../../../lib/loadState.js' -import { formatCurrencyBalance, formatCurrencyInputBalance } from '../../../lib/formatters.js' -import { tryParseBigIntListInput } from '../../../lib/inputs.js' -import { tryParseRepAmountInput as parseMigrationAmountInput } from '../../markets/lib/marketForm.js' -import { deriveTokenApprovalRequirement, type TokenApprovalState } from '../../../lib/tokenApproval.js' -import { getUniversePresentation } from '../../../lib/userCopy.js' +import type { LoadableValueState } from '@zoltar/ui-core-shared/lib/loadState.js' +import { formatCurrencyBalance, formatCurrencyInputBalance } from '@zoltar/ui-core-shared/lib/formatters.js' +import { tryParseBigIntListInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { tryParseRepAmountInput as parseMigrationAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { deriveTokenApprovalRequirement, type TokenApprovalState } from '@zoltar/ui-core-shared/lib/tokenApproval.js' +import { getUniversePresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' import { getMigrationGuardMessage } from '../lib/zoltarMigrationGuards.js' import type { ZoltarMigrationFormState } from '../../../types/app.js' -import type { ZoltarUniverseSummary } from '../../../types/contracts.js' -import { getWrongNetworkMessage } from '../../../lib/network.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWrongNetworkMessage } from '@zoltar/ui-core-shared/lib/network.js' type ZoltarMigrationSectionProps = { accountAddress: Address | undefined diff --git a/ui/ts/features/universes/hooks/useZoltarFork.ts b/ui/zoltar/ts/features/universes/hooks/useZoltarFork.ts similarity index 94% rename from ui/ts/features/universes/hooks/useZoltarFork.ts rename to ui/zoltar/ts/features/universes/hooks/useZoltarFork.ts index 612074448..e49ae6e56 100644 --- a/ui/ts/features/universes/hooks/useZoltarFork.ts +++ b/ui/zoltar/ts/features/universes/hooks/useZoltarFork.ts @@ -1,24 +1,24 @@ import { useSignal } from '@preact/signals' import { useCallback, useEffect, useRef } from 'preact/hooks' import { zeroAddress, type Address, type Hash } from '@zoltar/shared/ethereum' -import { ABIS } from '../../../abis.js' -import { Zoltar_Zoltar } from '../../../contractArtifact.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' +import { Zoltar_Zoltar } from '@zoltar/ui-core-shared/contractArtifact.js' import { approveErc20, forkZoltarUniverse, getZoltarAddress, readOptionalMulticall } from '../../../protocol/index.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { requireWallet } from '../../../lib/requireWalletConnection.js' -import { assertActiveWallet } from '../../../lib/assertActiveWallet.js' -import { formatRefreshErrorMessage, formatWriteErrorMessage, getErrorMessage } from '../../../lib/errors.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { requireWallet } from '@zoltar/ui-core-shared/lib/requireWalletConnection.js' +import { assertActiveWallet } from '@zoltar/ui-core-shared/lib/assertActiveWallet.js' +import { formatRefreshErrorMessage, formatWriteErrorMessage, getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createZoltarForkSuccessPresentation, createZoltarForkTransactionIntent, createZoltarForkWarningPresentation } from '../../transactionPresentations.js' -import { parseBigIntInput } from '../../markets/lib/marketForm.js' -import type { TokenApprovalState } from '../../../lib/tokenApproval.js' +import { parseBigIntInput } from '@zoltar/ui-core-shared/lib/integerInput.js' +import type { TokenApprovalState } from '@zoltar/ui-core-shared/lib/tokenApproval.js' import { getGenesisReputationTokenAddress } from '../lib/universe.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { WriteOperationsParameters } from '../../../types/app.js' -import type { ZoltarForkActionResult, ZoltarUniverseSummary } from '../../../types/contracts.js' +import type { ZoltarForkActionResult, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type UseZoltarForkParameters = { accountAddress: Address | undefined diff --git a/ui/ts/features/universes/hooks/useZoltarMigration.ts b/ui/zoltar/ts/features/universes/hooks/useZoltarMigration.ts similarity index 91% rename from ui/ts/features/universes/hooks/useZoltarMigration.ts rename to ui/zoltar/ts/features/universes/hooks/useZoltarMigration.ts index f1564452d..fdb69eade 100644 --- a/ui/ts/features/universes/hooks/useZoltarMigration.ts +++ b/ui/zoltar/ts/features/universes/hooks/useZoltarMigration.ts @@ -1,20 +1,21 @@ import { useSignal } from '@preact/signals' import { useCallback } from 'preact/hooks' -import { useFormState } from '../../../hooks/useFormState.js' +import { useFormState } from '@zoltar/ui-core-shared/hooks/useFormState.js' import type { Address, Hash } from '@zoltar/shared/ethereum' import { migrateInternalRepInZoltar, prepareRepForMigrationInZoltar } from '../../../protocol/index.js' -import { createWalletWriteClient } from '../../../lib/clients.js' -import { formatRefreshErrorMessage, formatWriteErrorMessage } from '../../../lib/errors.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { formatRefreshErrorMessage, formatWriteErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createZoltarMigrationSuccessPresentation, createZoltarMigrationTransactionIntent, createZoltarMigrationWarningPresentation } from '../../transactionPresentations.js' -import { requireWallet } from '../../../lib/requireWalletConnection.js' -import { assertActiveWallet } from '../../../lib/assertActiveWallet.js' -import { parseBigIntListInput } from '../../../lib/inputs.js' -import { getDefaultZoltarMigrationFormState, parseRepAmountInput } from '../../markets/lib/marketForm.js' -import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { requireWallet } from '@zoltar/ui-core-shared/lib/requireWalletConnection.js' +import { assertActiveWallet } from '@zoltar/ui-core-shared/lib/assertActiveWallet.js' +import { parseBigIntListInput } from '@zoltar/ui-core-shared/lib/inputs.js' +import { getDefaultZoltarMigrationFormState } from '../../../lib/formDefaults.js' +import { parseRepAmountInput } from '@zoltar/ui-core-shared/lib/formInputs.js' +import { refreshWalletStateOnly } from '@zoltar/ui-core-shared/lib/refreshState.js' import type { WriteOperationsParameters, ZoltarMigrationFormState } from '../../../types/app.js' -import type { ZoltarMigrationActionResult, ZoltarUniverseSummary } from '../../../types/contracts.js' +import type { ZoltarMigrationActionResult, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type UseZoltarMigrationParameters = { accountAddress: Address | undefined diff --git a/ui/ts/features/universes/hooks/useZoltarOperations.ts b/ui/zoltar/ts/features/universes/hooks/useZoltarOperations.ts similarity index 97% rename from ui/ts/features/universes/hooks/useZoltarOperations.ts rename to ui/zoltar/ts/features/universes/hooks/useZoltarOperations.ts index fa5f20b01..a578a1fbc 100644 --- a/ui/ts/features/universes/hooks/useZoltarOperations.ts +++ b/ui/zoltar/ts/features/universes/hooks/useZoltarOperations.ts @@ -1,7 +1,7 @@ import type { Address, Hash } from '@zoltar/shared/ethereum' import { useCallback, useMemo } from 'preact/hooks' import type { WriteOperationsParameters } from '../../../types/app.js' -import type { DeploymentStatus, ZoltarUniverseSummary } from '../../../types/contracts.js' +import type { DeploymentStatus, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' import { useZoltarFork } from './useZoltarFork.js' import { useZoltarMigration } from './useZoltarMigration.js' import { useZoltarUniverse } from './useZoltarUniverse.js' diff --git a/ui/ts/features/universes/hooks/useZoltarUniverse.ts b/ui/zoltar/ts/features/universes/hooks/useZoltarUniverse.ts similarity index 96% rename from ui/ts/features/universes/hooks/useZoltarUniverse.ts rename to ui/zoltar/ts/features/universes/hooks/useZoltarUniverse.ts index 41c20d67d..f92739c05 100644 --- a/ui/ts/features/universes/hooks/useZoltarUniverse.ts +++ b/ui/zoltar/ts/features/universes/hooks/useZoltarUniverse.ts @@ -2,19 +2,19 @@ import { useSignal } from '@preact/signals' import { useLayoutEffect, useRef } from 'preact/hooks' import type { Address, Hash } from '@zoltar/shared/ethereum' import { createZoltarChildUniverse, loadAllZoltarQuestions, loadMarketDetails, loadZoltarQuestionCount, loadZoltarQuestionPage, loadZoltarUniverseSummary } from '../../../protocol/index.js' -import { useLoadController } from '../../../hooks/useLoadController.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' -import { formatRefreshErrorMessage, formatWriteErrorMessage, getErrorMessage } from '../../../lib/errors.js' -import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../../../lib/actionFeedback.js' -import type { ActionFeedback } from '../../../lib/actionFeedback.js' +import { useLoadController } from '@zoltar/ui-core-shared/hooks/useLoadController.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { formatRefreshErrorMessage, formatWriteErrorMessage, getErrorMessage } from '@zoltar/ui-core-shared/lib/errors.js' +import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' +import type { ActionFeedback } from '@zoltar/ui-core-shared/lib/actionFeedback.js' import { createChildUniverseSuccessPresentation, createChildUniverseTransactionIntent, createChildUniverseWarningPresentation } from '../../transactionPresentations.js' -import { hasDeployedStep } from '../../markets/lib/marketCreation.js' -import { useRequestGuard } from '../../../lib/requestGuard.js' -import { requireWallet } from '../../../lib/requireWalletConnection.js' -import { normalizeQuestionId } from '../../../lib/questionId.js' -import { assertActiveWallet } from '../../../lib/assertActiveWallet.js' +import { hasDeployedStep } from '@zoltar/ui-core-shared/lib/deploymentStatus.js' +import { useRequestGuard } from '@zoltar/ui-core-shared/lib/requestGuard.js' +import { requireWallet } from '@zoltar/ui-core-shared/lib/requireWalletConnection.js' +import { normalizeQuestionId } from '@zoltar/ui-core-shared/lib/questionId.js' +import { assertActiveWallet } from '@zoltar/ui-core-shared/lib/assertActiveWallet.js' import type { WriteOperationsParameters } from '../../../types/app.js' -import type { DeploymentStatus, MarketDetails, MarketDetailsPage, ZoltarChildUniverseActionResult, ZoltarUniverseSummary } from '../../../types/contracts.js' +import type { DeploymentStatus, MarketDetails, MarketDetailsPage, ZoltarChildUniverseActionResult, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' function buildQuestionPageFromQuestions(questions: MarketDetails[], currentPage: MarketDetailsPage): MarketDetailsPage { const questionCount = BigInt(questions.length) diff --git a/ui/ts/features/universes/lib/universe.ts b/ui/zoltar/ts/features/universes/lib/universe.ts similarity index 95% rename from ui/ts/features/universes/lib/universe.ts rename to ui/zoltar/ts/features/universes/lib/universe.ts index 297fe97ee..78e35e4b7 100644 --- a/ui/ts/features/universes/lib/universe.ts +++ b/ui/zoltar/ts/features/universes/lib/universe.ts @@ -1,5 +1,5 @@ -import { buildRouteHref, getCurrentRouteHash, getRouteHashSearch } from '../../../lib/routing.js' -import { readUniverseQueryParam, writeUniverseQueryParam } from '../../../lib/urlParams.js' +import { buildRouteHref, getCurrentRouteHash, getRouteHashSearch } from '@zoltar/ui-core-shared/lib/routing.js' +import { readUniverseQueryParam, writeUniverseQueryParam } from '@zoltar/ui-core-shared/lib/urlParams.js' import { getGenesisReputationTokenAddress } from '../../../protocol/activeProtocolAddresses.js' export { getGenesisReputationTokenAddress } diff --git a/ui/ts/features/universes/lib/zoltarMigrationGuards.ts b/ui/zoltar/ts/features/universes/lib/zoltarMigrationGuards.ts similarity index 79% rename from ui/ts/features/universes/lib/zoltarMigrationGuards.ts rename to ui/zoltar/ts/features/universes/lib/zoltarMigrationGuards.ts index dc27ccf86..f186444e2 100644 --- a/ui/ts/features/universes/lib/zoltarMigrationGuards.ts +++ b/ui/zoltar/ts/features/universes/lib/zoltarMigrationGuards.ts @@ -1,6 +1,6 @@ import type { Address } from '@zoltar/shared/ethereum' -import type { ZoltarUniverseSummary } from '../../../types/contracts.js' -import { getWalletActiveAppChainGuardState } from '../../../lib/actionGuards.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { getWalletActiveAppChainGuardState } from '@zoltar/ui-core-shared/lib/actionGuards.js' export function getMigrationGuardMessage(accountAddress: Address | undefined, isOnActiveAppChain: boolean, rootUniverse: ZoltarUniverseSummary | undefined, loadingZoltarForkAccess: boolean, hasForked: boolean, loadingZoltarUniverse: boolean, notForkedAction: string): string | undefined { const walletGuardState = getWalletActiveAppChainGuardState({ accountAddress, isOnActiveAppChain }) diff --git a/ui/zoltar/ts/features/zoltarSurface/components/ZoltarSection.tsx b/ui/zoltar/ts/features/zoltarSurface/components/ZoltarSection.tsx new file mode 100644 index 000000000..68721ee86 --- /dev/null +++ b/ui/zoltar/ts/features/zoltarSurface/components/ZoltarSection.tsx @@ -0,0 +1,205 @@ +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' +import * as marketCopy from '../../../copy/market.js' +import * as zoltarCopy from '../../../copy/zoltar.js' +import { useState } from 'preact/hooks' +import type { Address } from '@zoltar/shared/ethereum' +import { ChildUniverseDeploymentModal } from '../../universes/components/ChildUniverseDeploymentModal.js' +import { ChildUniverseDetails } from '../../universes/components/ChildUniverseDetails.js' +import { ChildUniversesSection, ChildUniverseStatusBadge } from '../../universes/components/ChildUniversesSection.js' +import { ForkZoltarSection } from '../../universes/components/ForkZoltarSection.js' +import { ZoltarMigrationSection } from '../../universes/components/ZoltarMigrationSection.js' +import { DataGrid } from '@zoltar/ui-core-shared/components/DataGrid.js' +import { EntityCard } from '@zoltar/ui-core-shared/components/EntityCard.js' +import { MetricField } from '@zoltar/ui-core-shared/components/MetricField.js' +import { StateHint } from '@zoltar/ui-core-shared/components/StateHint.js' +import { RouteHeader } from '@zoltar/ui-core-shared/components/RouteHeader.js' +import { getWrongNetworkMessage, isActiveAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { getUniversePresentation } from '@zoltar/ui-core-shared/lib/userCopy.js' +import { formatUniverseCollectionLabel } from '../../universes/lib/universe.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import type { MarketRouteContentProps } from '../../types.js' + +type ZoltarUniverseOverviewProps = { + accountAddress: Address | undefined + isOnActiveAppChain: boolean + onCreateChildUniverseForOutcomeIndex: (outcomeIndex: bigint) => void + zoltarChildUniversePendingOutcomeIndex: bigint | undefined + zoltarUniverse: ZoltarUniverseSummary +} + +function ZoltarUniverseOverview({ accountAddress, isOnActiveAppChain, onCreateChildUniverseForOutcomeIndex, zoltarChildUniversePendingOutcomeIndex, zoltarUniverse }: ZoltarUniverseOverviewProps) { + const hasForked = zoltarUniverse.hasForked === true + const currentUniverseName = formatUniverseCollectionLabel([zoltarUniverse.universeId]) + const [selectedChildOutcomeIndex, setSelectedChildOutcomeIndex] = useState(undefined) + const selectedChildUniverse = zoltarUniverse.childUniverses.find(child => child.outcomeIndex === selectedChildOutcomeIndex) + const childDeploymentAvailabilityReason = (() => { + if (accountAddress === undefined) return marketCopy.childDeploymentWalletRequiredReason + if (!isOnActiveAppChain) return getWrongNetworkMessage() ?? commonCopy.mainnetRequiredReason + if (!hasForked) return marketCopy.childUniversesNotForkedReason + return undefined + })() + const childUniverseRequirements = [ + { key: 'forked', label: marketCopy.universeIsForked, resolved: hasForked, ...(hasForked ? {} : { detail: marketCopy.childUniversesNotForkedReason }) }, + { key: 'selection', label: marketCopy.childUniverseSelected, resolved: selectedChildUniverse !== undefined, ...(selectedChildUniverse === undefined ? { detail: marketCopy.childDeploymentSelectionRequired } : {}) }, + { key: 'wallet', label: marketCopy.walletConnected, resolved: accountAddress !== undefined, ...(accountAddress !== undefined ? {} : { detail: marketCopy.childDeploymentWalletRequiredReason }) }, + { key: 'exists', label: marketCopy.childUniverseNotAlreadyDeployed, resolved: selectedChildUniverse?.exists !== true, ...(selectedChildUniverse?.exists === true ? { detail: marketCopy.childUniverseDeployedReason } : {}) }, + ] + + return ( + <> + + {currentUniverseName} + {hasForked ? commonCopy.forked : marketCopy.unforked} + + ({ + availability: { + disabled: childDeploymentAvailabilityReason !== undefined || child.exists, + reason: childDeploymentAvailabilityReason ?? (child.exists ? marketCopy.childUniverseDeployedReason : undefined), + }, + label: child.exists ? commonCopy.deployed : marketCopy.createChildUniverse, + onClick: () => setSelectedChildOutcomeIndex(child.outcomeIndex), + pending: zoltarChildUniversePendingOutcomeIndex === child.outcomeIndex, + pendingLabel: commonCopy.opening, + })} + renderBadge={child => } + renderBody={child => } + surface='flat' + /> + setSelectedChildOutcomeIndex(undefined)} + onConfirm={() => { + if (selectedChildUniverse === undefined) return + onCreateChildUniverseForOutcomeIndex(selectedChildUniverse.outcomeIndex) + }} + pending={selectedChildUniverse !== undefined && zoltarChildUniversePendingOutcomeIndex === selectedChildUniverse.outcomeIndex} + pendingLabel={marketCopy.deployingUniverse} + requirements={childUniverseRequirements} + title={marketCopy.createChildUniverseTitle} + > + {selectedChildUniverse === undefined ? undefined : ( + + + + )} + + + ) +} + +export function ZoltarSection({ + accountState, + activeView, + loadingZoltarForkAccess, + loadingZoltarQuestion, + loadingZoltarQuestions, + loadingZoltarUniverse, + hasLoadedZoltarQuestions, + onApproveZoltarForkRep, + onCreateChildUniverseForOutcomeIndex, + onForkZoltar, + onLoadZoltarQuestion, + onMigrateInternalRep, + onPrepareRepForMigration, + onZoltarForkQuestionIdChange, + onZoltarMigrationFormChange, + zoltarChildUniversePendingOutcomeIndex, + zoltarForkActiveAction, + zoltarForkApproval, + zoltarForkError, + zoltarForkPending, + zoltarForkQuestionId, + zoltarForkRepBalanceAttoRep, + zoltarMigrationActiveAction, + zoltarMigrationChildRepBalancesAttoRep, + zoltarMigrationError, + zoltarMigrationForm, + zoltarMigrationPending, + zoltarMigrationPreparedRepBalanceAttoRep, + zoltarQuestionLookupError, + zoltarQuestionLookupId, + zoltarQuestions, + zoltarUniverse, + zoltarUniverseState, +}: MarketRouteContentProps) { + const isOnActiveAppChain = isActiveAppChain(accountState.chainId) + if (zoltarUniverseState === 'missing') { + const presentation = getUniversePresentation(zoltarUniverseState) + return presentation === undefined ? undefined : + } + if (zoltarUniverse === undefined) { + return + } + + if (activeView === 'fork') { + return ( + <> + + onApproveZoltarForkRep(amount)} + onForkZoltar={onForkZoltar} + onRetryZoltarQuestion={zoltarForkQuestionId.trim() === '' ? undefined : () => void onLoadZoltarQuestion(zoltarForkQuestionId.trim())} + onZoltarForkQuestionIdChange={onZoltarForkQuestionIdChange} + zoltarForkActiveAction={zoltarForkActiveAction} + zoltarForkApproval={zoltarForkApproval} + zoltarForkError={zoltarForkError} + zoltarForkPending={zoltarForkPending} + zoltarForkQuestionId={zoltarForkQuestionId} + zoltarForkRepBalanceAttoRep={zoltarForkRepBalanceAttoRep} + zoltarQuestionLookupError={zoltarQuestionLookupError} + zoltarQuestionLookupId={zoltarQuestionLookupId} + zoltarQuestions={zoltarQuestions} + zoltarUniverse={zoltarUniverse} + zoltarUniverseState={zoltarUniverseState} + /> + + ) + } + + if (activeView === 'migrate') { + return ( + <> + + onApproveZoltarForkRep(amount)} + onMigrateInternalRep={onMigrateInternalRep} + onPrepareRepForMigration={onPrepareRepForMigration} + onZoltarMigrationFormChange={onZoltarMigrationFormChange} + zoltarForkActiveAction={zoltarForkActiveAction} + zoltarForkApproval={zoltarForkApproval} + zoltarForkRepBalanceAttoRep={zoltarForkRepBalanceAttoRep} + zoltarMigrationActiveAction={zoltarMigrationActiveAction} + zoltarMigrationChildRepBalancesAttoRep={zoltarMigrationChildRepBalancesAttoRep} + zoltarMigrationError={zoltarMigrationError} + zoltarMigrationForm={zoltarMigrationForm} + zoltarMigrationPending={zoltarMigrationPending} + zoltarMigrationPreparedRepBalanceAttoRep={zoltarMigrationPreparedRepBalanceAttoRep} + zoltarUniverse={zoltarUniverse} + zoltarUniverseState={zoltarUniverseState} + /> + + ) + } + + return +} diff --git a/ui/zoltar/ts/index.dev.ts b/ui/zoltar/ts/index.dev.ts new file mode 100644 index 000000000..30bb290d3 --- /dev/null +++ b/ui/zoltar/ts/index.dev.ts @@ -0,0 +1,16 @@ +import './liveReload.js' +import { createElement } from 'preact' +import { mountApp } from '@zoltar/ui-core-shared/app/appRoot.js' +import { installRouting } from '@zoltar/ui-core-shared/lib/routing.js' +import { App } from './app/App.js' + +installRouting({ + defaultRoute: 'zoltar', + routes: [ + { hash: '#/deploy', name: 'deploy' }, + { hash: '#/zoltar', name: 'zoltar', queryParameters: new Set(['universe', 'zoltarView']) }, + { hash: '#/open-oracle', name: 'open-oracle', queryParameters: new Set(['openOracleView', 'openOracleReportId']) }, + ], +}) + +void mountApp({ root: () => createElement(App, {}) }) diff --git a/ui/zoltar/ts/index.ts b/ui/zoltar/ts/index.ts new file mode 100644 index 000000000..ba9946f12 --- /dev/null +++ b/ui/zoltar/ts/index.ts @@ -0,0 +1,15 @@ +import { createElement } from 'preact' +import { mountApp } from '@zoltar/ui-core-shared/app/appRoot.js' +import { installRouting } from '@zoltar/ui-core-shared/lib/routing.js' +import { App } from './app/App.js' + +installRouting({ + defaultRoute: 'zoltar', + routes: [ + { hash: '#/deploy', name: 'deploy' }, + { hash: '#/zoltar', name: 'zoltar', queryParameters: new Set(['universe', 'zoltarView']) }, + { hash: '#/open-oracle', name: 'open-oracle', queryParameters: new Set(['openOracleView', 'openOracleReportId']) }, + ], +}) + +void mountApp({ root: () => createElement(App, {}) }) diff --git a/ui/zoltar/ts/lib/formDefaults.ts b/ui/zoltar/ts/lib/formDefaults.ts new file mode 100644 index 000000000..b9b16de32 --- /dev/null +++ b/ui/zoltar/ts/lib/formDefaults.ts @@ -0,0 +1,57 @@ +import type { OpenOracleCreateFormState, OpenOracleFormState, ReportingFormState, ReportingWithdrawDepositIndexesByOutcome, ZoltarMigrationFormState } from '../types/app.js' + +const DEFAULT_OPEN_ORACLE_DISPUTE_DELAY_SECONDS = '3600' +const DEFAULT_OPEN_ORACLE_SETTLEMENT_DELAY_SECONDS = '86400' + +export function getDefaultOpenOracleFormState(): OpenOracleFormState { + return { + amount1: '0', + amount2: '0', + disputeNewAmount1: '0', + disputeNewAmount2: '0', + disputeTokenToSwap: 'token1', + reportId: '', + stateHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + } +} + +export function getDefaultOpenOracleCreateFormState(): OpenOracleCreateFormState { + return { + disputeDelay: DEFAULT_OPEN_ORACLE_DISPUTE_DELAY_SECONDS, + escalationHalt: '0', + exactToken1Report: '0', + initialToken2Amount: '0', + ethValue: '0', + feePercentage: '0', + multiplier: '100', + protocolFee: '0', + settlementTime: DEFAULT_OPEN_ORACLE_SETTLEMENT_DELAY_SECONDS, + settlerRewardEthAmount: '0', + token1Address: '', + token2Address: '', + } +} + +export function getDefaultReportingWithdrawDepositIndexesByOutcome(): ReportingWithdrawDepositIndexesByOutcome { + return { + invalid: [], + yes: [], + no: [], + } +} + +export function getDefaultReportingFormState(): ReportingFormState { + return { + reportAmount: '0', + securityPoolAddress: '', + selectedOutcome: undefined, + selectedWithdrawDepositIndexesByOutcome: getDefaultReportingWithdrawDepositIndexesByOutcome(), + } +} + +export function getDefaultZoltarMigrationFormState(): ZoltarMigrationFormState { + return { + amount: '0.0', + outcomeIndexes: '', + } +} diff --git a/ui/zoltar/ts/liveReload.ts b/ui/zoltar/ts/liveReload.ts new file mode 100644 index 000000000..f2eef3c95 --- /dev/null +++ b/ui/zoltar/ts/liveReload.ts @@ -0,0 +1,9 @@ +const SHOULD_ENABLE_LIVE_RELOAD = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') && typeof EventSource !== 'undefined' + +if (SHOULD_ENABLE_LIVE_RELOAD) { + const reloadEvents = new EventSource('/__live-reload') + + reloadEvents.addEventListener('reload', () => { + window.location.reload() + }) +} diff --git a/ui/ts/protocol/README.md b/ui/zoltar/ts/protocol/README.md similarity index 52% rename from ui/ts/protocol/README.md rename to ui/zoltar/ts/protocol/README.md index 7eae28ee9..845f3e25f 100644 --- a/ui/ts/protocol/README.md +++ b/ui/zoltar/ts/protocol/README.md @@ -2,6 +2,6 @@ This directory owns typed reads and writes against Zoltar and Statoblast contracts. `index.ts` is the compatibility entrypoint; implementations belong in domain modules instead of the barrel. -UI components, hooks, and feature libraries may consume the protocol client. Dependencies must not point in the other direction: protocol modules never import from `ui/ts/features`. +UI components, hooks, and feature libraries may consume the protocol client. Dependencies must not point in the other direction: protocol modules never import from `ui/zoltar/ts/features`. -Calculations that prepare or interpret contract calls belong here when they are shared with a feature. General UI-independent helpers belong in `ui/ts/lib`. The UI-layer boundary lint rejects static, dynamic, exported, and type imports that point upward across protocol, feature, app, and shared-component ownership. +Calculations that prepare or interpret contract calls belong here when they are shared with a feature. General cross-app helpers belong in `ui/coreShared/ts/lib`; Zoltar-specific helpers belong in `ui/zoltar/ts/lib`. The UI-layer boundary lint rejects static, dynamic, exported, and type imports that point upward across protocol, feature, app, and shared-component ownership. diff --git a/ui/ts/protocol/activeProtocolAddresses.ts b/ui/zoltar/ts/protocol/activeProtocolAddresses.ts similarity index 69% rename from ui/ts/protocol/activeProtocolAddresses.ts rename to ui/zoltar/ts/protocol/activeProtocolAddresses.ts index c81a08974..c2f65a04b 100644 --- a/ui/ts/protocol/activeProtocolAddresses.ts +++ b/ui/zoltar/ts/protocol/activeProtocolAddresses.ts @@ -1,4 +1,4 @@ -import { getRuntimeNetworkProfile } from '../lib/networkProfile.js' +import { getRuntimeNetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' export function getGenesisReputationTokenAddress() { return getRuntimeNetworkProfile().genesisRepTokenAddress diff --git a/ui/ts/protocol/contractLabels.ts b/ui/zoltar/ts/protocol/contractLabels.ts similarity index 93% rename from ui/ts/protocol/contractLabels.ts rename to ui/zoltar/ts/protocol/contractLabels.ts index 8215cbaf2..d66369929 100644 --- a/ui/ts/protocol/contractLabels.ts +++ b/ui/zoltar/ts/protocol/contractLabels.ts @@ -1,4 +1,4 @@ -import { ABIS } from '../abis.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' import { ZoltarQuestionData_ZoltarQuestionData, Zoltar_Zoltar, @@ -9,7 +9,7 @@ import { statoblast_factories_SecurityPoolFactory_SecurityPoolFactory, statoblast_openOracle_OpenOracle_OpenOracle, statoblast_tokens_ShareToken_ShareToken, -} from '../contractArtifact.js' +} from '@zoltar/ui-core-shared/contractArtifact.js' const CONTRACT_LABEL_BY_ABI = new Map([ [ABIS.mainnet.erc20, 'ERC-20 Token'], diff --git a/ui/ts/protocol/core.ts b/ui/zoltar/ts/protocol/core.ts similarity index 97% rename from ui/ts/protocol/core.ts rename to ui/zoltar/ts/protocol/core.ts index 49838d283..148059f8e 100644 --- a/ui/ts/protocol/core.ts +++ b/ui/zoltar/ts/protocol/core.ts @@ -1,7 +1,7 @@ import { encodeFunctionData, RpcError, type Abi, type Account, type Address, type ContractFunctionParameters, type Hash, type MulticallReturnType, type ReplacementReason, type TransactionReceipt } from '@zoltar/shared/ethereum' import { getMulticall3Address } from './deploymentHelpers.js' -import type { ReadClient, WriteClient } from '../types/contracts.js' -import type { TransactionRequestPreview } from '../lib/chainBackend.js' +import type { ReadClient, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import type { TransactionRequestPreview } from '@zoltar/ui-core-shared/lib/chainBackend.js' import { getContractLabel } from './contractLabels.js' const RPC_STATE_RETRY_DELAYS_MILLISECONDS = [250, 500, 1_000, 2_000, 4_000] as const diff --git a/ui/ts/protocol/decoders.ts b/ui/zoltar/ts/protocol/decoders.ts similarity index 100% rename from ui/ts/protocol/decoders.ts rename to ui/zoltar/ts/protocol/decoders.ts diff --git a/ui/ts/protocol/deployment.ts b/ui/zoltar/ts/protocol/deployment.ts similarity index 90% rename from ui/ts/protocol/deployment.ts rename to ui/zoltar/ts/protocol/deployment.ts index ce771b5f1..0b48fcfe8 100644 --- a/ui/ts/protocol/deployment.ts +++ b/ui/zoltar/ts/protocol/deployment.ts @@ -1,35 +1,22 @@ import { encodeDeployData, getAddress, keccak256, type Address, type Hash, type Hex } from '@zoltar/shared/ethereum' -import { ABIS } from '../abis.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' import { createDeploymentStatusOracleAddressHelper } from '@zoltar/shared/deploymentAddresses' import { DeploymentStatusOracle_DeploymentStatusOracle, ScalarOutcomes_ScalarOutcomes, ZoltarQuestionData_ZoltarQuestionData, - statoblast_EscalationGameClaimDelegate_EscalationGameClaimDelegate, statoblast_Multicall3_Multicall3, statoblast_SecurityPoolUtils_SecurityPoolUtils, statoblast_WETH9_WETH9, statoblast_factories_UniformPriceDualCapBatchAuctionFactory_UniformPriceDualCapBatchAuctionFactory, statoblast_openOracle_OpenOracle_OpenOracle, -} from '../contractArtifact.js' -import { - MULTICALL3_BYTECODE, - PROXY_DEPLOYER_ADDRESS, - ZERO_SALT, - getEscalationGameFactoryByteCode, - getInfraContractAddresses, - getPriceOracleManagerAndOperatorQueuerFactoryByteCode, - getSecurityPoolFactoryByteCode, - getSecurityPoolForkerByteCode, - getShareTokenFactoryByteCode, - getZoltarInitCode, - getZoltarQuestionDataByteCode, -} from './deploymentHelpers.js' +} from '@zoltar/ui-core-shared/contractArtifact.js' +import { MULTICALL3_BYTECODE, PROXY_DEPLOYER_ADDRESS, ZERO_SALT, getInfraContractAddresses, getPriceOracleManagerAndOperatorQueuerFactoryByteCode, getShareTokenFactoryByteCode, getZoltarInitCode, getZoltarQuestionDataByteCode } from './deploymentHelpers.js' import { readWithRpcStateRetries, waitForSubmittedTransactionReceipt, type RpcStateRetryWait } from './core.js' -import type { DeploymentStatusSnapshot, DeploymentStep, DeploymentStepId, ReadClient, WriteClient } from '../types/contracts.js' -import type { TransactionRequestPreview } from '../lib/chainBackend.js' -import { getRuntimeNetworkProfile, type NetworkProfile } from '../lib/networkProfile.js' -import { SEPOLIA_GENESIS_REP_INIT_CODE, SEPOLIA_WETH_INIT_CODE } from '../lib/sepoliaDeploymentConfig.js' +import type { DeploymentStatusSnapshot, DeploymentStep, DeploymentStepId, ReadClient, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import type { TransactionRequestPreview } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import { getRuntimeNetworkProfile, type NetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { SEPOLIA_GENESIS_REP_INIT_CODE, SEPOLIA_WETH_INIT_CODE } from '@zoltar/ui-core-shared/lib/sepoliaDeploymentConfig.js' const PROXY_DEPLOYER_SIGNER = getAddress('0x4c8d290a1b368ac4728d83a9e8321fc3af2b39b1') const PROXY_DEPLOYER_RAW_TRANSACTION = '0xf87e8085174876e800830186a08080ad601f80600e600039806000f350fe60003681823780368234f58015156014578182fd5b80825250506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222' satisfies Hex @@ -62,7 +49,6 @@ export const EXPECTED_SEPOLIA_DEPLOYMENT_RUNTIME_CODE_HASHES: Readonly step.deployed), + applicationDeploymentComplete: deploymentStatuses.every(step => step.deployed), deploymentStatuses, } } @@ -544,48 +526,11 @@ export function getDeploymentSteps(profile: NetworkProfile = getRuntimeNetworkPr dependencies: [...(profile.id === 'sepolia' ? (['weth'] as const) : []), 'proxyDeployer'], deploy: async client => await deployViaProxy(client, getPriceOracleManagerAndOperatorQueuerFactoryByteCode(profile.wethAddress)), }, - { - id: 'securityPoolForker', - label: 'Security Pool Forker', - address: addresses.securityPoolForker, - dependencies: ['proxyDeployer', 'scalarOutcomes', 'securityPoolUtils', 'zoltar'], - deploy: async client => await deployViaProxy(client, getSecurityPoolForkerByteCode(addresses.zoltar)), - }, - { - id: 'escalationGameClaimDelegate', - label: 'Escalation Claim Checkpoint Delegate', - address: addresses.escalationGameClaimDelegate, - dependencies: ['proxyDeployer'], - deploy: async client => await deployViaProxy(client, `0x${statoblast_EscalationGameClaimDelegate_EscalationGameClaimDelegate.evm.bytecode.object}`), - }, - { - id: 'escalationGameFactory', - label: 'Escalation Game Factory', - address: addresses.escalationGameFactory, - dependencies: ['proxyDeployer', 'escalationGameClaimDelegate'], - deploy: async client => await deployViaProxy(client, getEscalationGameFactoryByteCode(addresses.escalationGameClaimDelegate)), - }, - { - id: 'securityPoolFactory', - label: 'Security Pool Factory', - address: addresses.securityPoolFactory, - dependencies: ['proxyDeployer', 'securityPoolForker', 'zoltarQuestionData', 'escalationGameFactory', 'openOracle', 'zoltar', 'shareTokenFactory', 'uniformPriceDualCapBatchAuctionFactory', 'priceOracleManagerAndOperatorQueuerFactory', 'securityPoolUtils'], - deploy: async client => - await deployViaProxy( - client, - getSecurityPoolFactoryByteCode({ - escalationGameFactory: addresses.escalationGameFactory, - openOracle: addresses.openOracle, - priceOracleManagerAndOperatorQueuerFactory: addresses.priceOracleManagerAndOperatorQueuerFactory, - securityPoolForker: addresses.securityPoolForker, - shareTokenFactory: addresses.shareTokenFactory, - uniformPriceDualCapBatchAuctionFactory: addresses.uniformPriceDualCapBatchAuctionFactory, - zoltar: addresses.zoltar, - zoltarQuestionData: addresses.zoltarQuestionData, - }), - ), - }, ] + return withExpectedDeploymentRuntimeCodeHashes(steps, profile) +} + +export function withExpectedDeploymentRuntimeCodeHashes(steps: readonly DeploymentStep[], profile: NetworkProfile): DeploymentStep[] { return steps.map(step => ({ ...step, ...(profile.id === 'sepolia' ? { expectedRuntimeCodeHash: EXPECTED_SEPOLIA_DEPLOYMENT_RUNTIME_CODE_HASHES[step.id] } : {}), diff --git a/ui/ts/protocol/deploymentHelpers.ts b/ui/zoltar/ts/protocol/deploymentHelpers.ts similarity index 98% rename from ui/ts/protocol/deploymentHelpers.ts rename to ui/zoltar/ts/protocol/deploymentHelpers.ts index 2b7e959e3..6f2d6e0e6 100644 --- a/ui/ts/protocol/deploymentHelpers.ts +++ b/ui/zoltar/ts/protocol/deploymentHelpers.ts @@ -17,9 +17,9 @@ import { statoblast_factories_ShareTokenFactory_ShareTokenFactory, statoblast_factories_UniformPriceDualCapBatchAuctionFactory_UniformPriceDualCapBatchAuctionFactory, statoblast_openOracle_OpenOracle_OpenOracle, -} from '../contractArtifact.js' +} from '@zoltar/ui-core-shared/contractArtifact.js' import { getGenesisReputationTokenAddress, getWethAddress } from './activeProtocolAddresses.js' -import { getRuntimeNetworkProfile, type NetworkProfile } from '../lib/networkProfile.js' +import { getRuntimeNetworkProfile, type NetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' export { OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE } from '@zoltar/shared/oracleInitialReport' diff --git a/ui/ts/protocol/forkActivity.ts b/ui/zoltar/ts/protocol/forkActivity.ts similarity index 80% rename from ui/ts/protocol/forkActivity.ts rename to ui/zoltar/ts/protocol/forkActivity.ts index 045f687a1..65c68ef9a 100644 --- a/ui/ts/protocol/forkActivity.ts +++ b/ui/zoltar/ts/protocol/forkActivity.ts @@ -1,4 +1,4 @@ -import type { ListedSecurityPool } from '../types/contracts.js' +import type { ListedSecurityPool } from '@zoltar/ui-core-shared/types/contracts.js' type ForkActivitySource = Pick diff --git a/ui/ts/protocol/forkData.ts b/ui/zoltar/ts/protocol/forkData.ts similarity index 100% rename from ui/ts/protocol/forkData.ts rename to ui/zoltar/ts/protocol/forkData.ts diff --git a/ui/ts/protocol/forks.ts b/ui/zoltar/ts/protocol/forks.ts similarity index 98% rename from ui/ts/protocol/forks.ts rename to ui/zoltar/ts/protocol/forks.ts index c1dbafd4a..f8ac99eb6 100644 --- a/ui/ts/protocol/forks.ts +++ b/ui/zoltar/ts/protocol/forks.ts @@ -1,13 +1,13 @@ import { zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { ABIS } from '../abis.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' import { deriveHasForkActivity } from './forkActivity.js' -import { Zoltar_Zoltar, statoblast_SecurityPoolForker_SecurityPoolForker, statoblast_SecurityPool_SecurityPool, statoblast_UniformPriceDualCapBatchAuction_UniformPriceDualCapBatchAuction } from '../contractArtifact.js' -import type { DeploymentStepId, ForkAuctionActionResult, ForkAuctionDetails, ReadClient, ReportingOutcomeKey, TruthAuctionMetrics, WriteClient, ZoltarChildUniverseActionResult, ZoltarForkActionResult, ZoltarMigrationActionResult } from '../types/contracts.js' +import { Zoltar_Zoltar, statoblast_SecurityPoolForker_SecurityPoolForker, statoblast_SecurityPool_SecurityPool, statoblast_UniformPriceDualCapBatchAuction_UniformPriceDualCapBatchAuction } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { DeploymentStepId, ForkAuctionActionResult, ForkAuctionDetails, ReadClient, ReportingOutcomeKey, TruthAuctionMetrics, WriteClient, ZoltarChildUniverseActionResult, ZoltarForkActionResult, ZoltarMigrationActionResult } from '@zoltar/ui-core-shared/types/contracts.js' import { getForkOutcomeKey, getQuestionIdHex, getReportingOutcomeKey, getReportingOutcomeValue, getSecurityPoolSystemState, hasTimestamp } from './helpers.js' import { type ContractRevertReasonParams, readRequiredMulticall, writeContractAndWait } from './core.js' import { getInfraContractAddresses, getZoltarAddress } from './deploymentHelpers.js' import { requireForkDataView } from './forkData.js' -import { executeForkAuctionAction } from './securityPoolActions.js' +import { executeForkAuctionAction } from '@zoltar/ui-core-shared/protocol/securityPoolActions.js' import { getDeploymentSteps } from './deployment.js' import { loadMarketDetails } from './zoltar.js' diff --git a/ui/ts/protocol/helpers.ts b/ui/zoltar/ts/protocol/helpers.ts similarity index 99% rename from ui/ts/protocol/helpers.ts rename to ui/zoltar/ts/protocol/helpers.ts index bcd4b075d..7d49662ff 100644 --- a/ui/ts/protocol/helpers.ts +++ b/ui/zoltar/ts/protocol/helpers.ts @@ -1,5 +1,5 @@ import { encodeAbiParameters, getAddress, keccak256, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import type { ForkOutcomeKey, MarketType, QuestionData, ReportingOutcomeKey, SecurityPoolSystemState } from '../types/contracts.js' +import type { ForkOutcomeKey, MarketType, QuestionData, ReportingOutcomeKey, SecurityPoolSystemState } from '@zoltar/ui-core-shared/types/contracts.js' type IntegerLike = bigint | number diff --git a/ui/ts/protocol/index.ts b/ui/zoltar/ts/protocol/index.ts similarity index 58% rename from ui/ts/protocol/index.ts rename to ui/zoltar/ts/protocol/index.ts index ed5111bb5..2b2274c82 100644 --- a/ui/ts/protocol/index.ts +++ b/ui/zoltar/ts/protocol/index.ts @@ -1,13 +1,8 @@ export { getDeploymentSteps, loadDeploymentStatusOracleSnapshot, loadErc20Allowance, loadErc20Balance } from './deployment.js' -export { createSecurityPool, loadAllSecurityPools, loadSecurityPoolPage, loadSecurityPoolVaultSummary, loadSecurityVaultDetails, originSecurityPoolExists } from './securityPools.js' export { createMarket, loadAllZoltarQuestions, loadMarketDetails, loadZoltarQuestionCount, loadZoltarQuestionPage, loadZoltarUniverseSummary } from './zoltar.js' export { buildForkCarriedEscalationProofs, loadEscalationDeposits, loadReportingDetails, reportOutcomeInSecurityPool, withdrawEscalationFromSecurityPool, withdrawForkedEscalationDeposits } from './reporting.js' -export { loadTruthAuctionActiveTickPage, loadTruthAuctionBidderBidPage, loadTruthAuctionTickBidPage, loadTruthAuctionTickPage, loadTruthAuctionTickSummary } from './truthAuctions.js' export { readOptionalMulticall } from './core.js' export { getMulticall3Address, getOpenOracleAddress, getZoltarAddress } from './deploymentHelpers.js' export * from './forks.js' export * from './openOracle.js' -export * from './securityVault.js' -export * from './tokenActions.js' -export * from './trading.js' -export * from './truthAuctionActions.js' +export { approveErc20 } from './tokenActions.js' diff --git a/ui/ts/protocol/openOracle.ts b/ui/zoltar/ts/protocol/openOracle.ts similarity index 98% rename from ui/ts/protocol/openOracle.ts rename to ui/zoltar/ts/protocol/openOracle.ts index b1fa29178..b12b7a30b 100644 --- a/ui/ts/protocol/openOracle.ts +++ b/ui/zoltar/ts/protocol/openOracle.ts @@ -1,8 +1,8 @@ import { bigintToSafeNumber, decodeEventLog, getAddress, zeroAddress, type Address, type Hex, type TransactionReceipt } from '@zoltar/shared/ethereum' import { getOpenOracleGameTuple, getOpenOracleHelperTuple, hasOpenOracleFlag, hashOpenOracleStatePreimage, OPEN_ORACLE_FLAG_STORE_ALL, OPEN_ORACLE_FLAG_STORE_PRICE, OPEN_ORACLE_FLAG_TIME_TYPE, OPEN_ORACLE_FLAG_TRACK_DISPUTES, type OpenOracleStatePreimage } from '@zoltar/shared/openOracle' -import { ABIS } from '../abis.js' -import { sameAddress } from '../lib/address.js' -import { isIgnorableLogDecodeError } from '../lib/errors.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import { isIgnorableLogDecodeError } from '@zoltar/ui-core-shared/lib/errors.js' import { resolveOracleOperationEthFunding } from './oracleRequestFunding.js' import { getOracleManagerPriceValidUntilTimestamp } from './oracleTiming.js' import { addOpenOracleBountyBuffer, addOpenOracleInitialReportFundingBuffer, getOpenOracleDisputeSwapTokenKey } from './openOracleMath.js' @@ -10,8 +10,20 @@ import { loadOpenOracleInitialReportPrice } from './openOraclePricing.js' import { getOpenOracleCreateParameterValidationMessage } from './openOracleValidation.js' import { decodeOracleQueueOperation, encodeOracleQueueOperation } from './oracleQueueOperation.js' import { getWethAddress } from './uniswapQuoter.js' -import { statoblast_LiquidationApprovalRegistry_LiquidationApprovalRegistry, statoblast_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator, statoblast_openOracle_OpenOracle_OpenOracle } from '../contractArtifact.js' -import type { LiquidationApprovalDetails, OpenOracleActionResult, OpenOracleWithdrawableBalances, OracleManagerDetails, OracleQueueOperation, ReadClient, OpenOracleReportSummary, OpenOracleReportSummaryPage, StagedOracleExecutionResult, StagedOracleQueuedResult, WriteClient } from '../types/contracts.js' +import { statoblast_LiquidationApprovalRegistry_LiquidationApprovalRegistry, statoblast_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator, statoblast_openOracle_OpenOracle_OpenOracle } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { + LiquidationApprovalDetails, + OpenOracleActionResult, + OpenOracleWithdrawableBalances, + OracleManagerDetails, + OracleQueueOperation, + ReadClient, + OpenOracleReportSummary, + OpenOracleReportSummaryPage, + StagedOracleExecutionResult, + StagedOracleQueuedResult, + WriteClient, +} from '@zoltar/ui-core-shared/types/contracts.js' import { getProtocolPageOffset, hasTimestampAndNumber, requireStagedOperationTupleArray } from './helpers.js' import { type WriteContractClient, readRequiredMulticall, writeContractAndWait, writeContractAndWaitForReceipt } from './core.js' import { getInfraContractAddresses, getOpenOracleAddress } from './deploymentHelpers.js' @@ -198,8 +210,8 @@ export async function loadOracleManagerDetails(client: ReadClient, managerAddres const resolvedOracleAddress = openOracleAddress ?? getInfraContractAddresses().openOracle let callbackStateHash: Hex | undefined let exactToken1Report: bigint | undefined - let pendingOperation: import('../types/contracts.js').StagedOracleOperation | undefined - let stagedOperations: import('../types/contracts.js').StagedOracleOperation[] = [] + let pendingOperation: import('@zoltar/ui-core-shared/types/contracts.js').StagedOracleOperation | undefined + let stagedOperations: import('@zoltar/ui-core-shared/types/contracts.js').StagedOracleOperation[] = [] let token1: Address | undefined let token2: Address | undefined if (activeStagedOperationCount > 0n) { @@ -294,7 +306,7 @@ function calculateOpenOraclePrice(amount1: bigint, amount2: bigint) { return amount2 === 0n ? 0n : (amount1 * 10n ** OPEN_ORACLE_PRICE_UNITS) / amount2 } -export async function loadOpenOracleReportDetails(client: ReadClient, openOracleAddress: Address, reportId: bigint): Promise { +export async function loadOpenOracleReportDetails(client: ReadClient, openOracleAddress: Address, reportId: bigint): Promise { const [eventState, stateHash, block] = await Promise.all([ loadOpenOracleEventState(client, openOracleAddress, reportId).catch(error => { if (error instanceof Error && error.message === `Oracle report #${reportId.toString()} does not exist`) throw createOpenOracleReportMissingError(reportId) diff --git a/ui/ts/protocol/openOracleMath.ts b/ui/zoltar/ts/protocol/openOracleMath.ts similarity index 100% rename from ui/ts/protocol/openOracleMath.ts rename to ui/zoltar/ts/protocol/openOracleMath.ts diff --git a/ui/ts/protocol/openOraclePricing.ts b/ui/zoltar/ts/protocol/openOraclePricing.ts similarity index 96% rename from ui/ts/protocol/openOraclePricing.ts rename to ui/zoltar/ts/protocol/openOraclePricing.ts index 22e63cad8..33d953ee4 100644 --- a/ui/ts/protocol/openOraclePricing.ts +++ b/ui/zoltar/ts/protocol/openOraclePricing.ts @@ -1,5 +1,5 @@ -import { getErrorDetail } from '../lib/errors.js' -import { getActiveNetworkProfile } from '../lib/activeEnvironment.js' +import { getErrorDetail } from '@zoltar/ui-core-shared/lib/errors.js' +import { getActiveNetworkProfile } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' import { isRepPricingEnabled, quoteBestExactInputWithSource, quoteBestV3ExactInputWithSource, quoteExactInput } from './uniswapQuoter.js' const OPEN_ORACLE_PRICE_PRECISION = 10n ** 30n diff --git a/ui/ts/protocol/openOracleState.ts b/ui/zoltar/ts/protocol/openOracleState.ts similarity index 98% rename from ui/ts/protocol/openOracleState.ts rename to ui/zoltar/ts/protocol/openOracleState.ts index 6f5631c67..e6f44551b 100644 --- a/ui/ts/protocol/openOracleState.ts +++ b/ui/zoltar/ts/protocol/openOracleState.ts @@ -1,6 +1,6 @@ import { decodeOpenOracleStatePreimage, hasOpenOracleFlag, OPEN_ORACLE_FLAG_TIME_TYPE, OPEN_ORACLE_REPORT_DISPUTED_TOPIC, OPEN_ORACLE_REPORT_SETTLED_TOPIC, OPEN_ORACLE_REPORT_SUBMITTED_TOPIC, getOpenOracleReportIdFromTopic, type OpenOracleStatePreimage } from '@zoltar/shared/openOracle' import type { Address, Hex, TransactionLog } from '@zoltar/shared/ethereum' -import type { ReadClient } from '../types/contracts.js' +import type { ReadClient } from '@zoltar/ui-core-shared/types/contracts.js' export type OpenOracleEventState = { initial: OpenOracleStatePreimage diff --git a/ui/ts/protocol/openOracleValidation.ts b/ui/zoltar/ts/protocol/openOracleValidation.ts similarity index 98% rename from ui/ts/protocol/openOracleValidation.ts rename to ui/zoltar/ts/protocol/openOracleValidation.ts index 30dc24cd6..3772acb8e 100644 --- a/ui/ts/protocol/openOracleValidation.ts +++ b/ui/zoltar/ts/protocol/openOracleValidation.ts @@ -1,4 +1,4 @@ -import { sameAddress } from '../lib/address.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' import { zeroAddress, type Address } from '@zoltar/shared/ethereum' export const OPEN_ORACLE_PERCENTAGE_PRECISION = 10n ** 7n diff --git a/ui/ts/protocol/oracleQueueOperation.ts b/ui/zoltar/ts/protocol/oracleQueueOperation.ts similarity index 82% rename from ui/ts/protocol/oracleQueueOperation.ts rename to ui/zoltar/ts/protocol/oracleQueueOperation.ts index f7ad5856a..76841d4b6 100644 --- a/ui/ts/protocol/oracleQueueOperation.ts +++ b/ui/zoltar/ts/protocol/oracleQueueOperation.ts @@ -1,5 +1,5 @@ -import { assertNever } from '../lib/assert.js' -import type { OracleQueueOperation } from '../types/contracts.js' +import { assertNever } from '@zoltar/ui-core-shared/lib/assert.js' +import type { OracleQueueOperation } from '@zoltar/ui-core-shared/types/contracts.js' export const LIQUIDATION_OPERATION_TYPE = 0 export const WITHDRAW_REP_OPERATION_TYPE = 1 diff --git a/ui/ts/protocol/oracleRequestFunding.ts b/ui/zoltar/ts/protocol/oracleRequestFunding.ts similarity index 91% rename from ui/ts/protocol/oracleRequestFunding.ts rename to ui/zoltar/ts/protocol/oracleRequestFunding.ts index ba8dea436..2cc2928e1 100644 --- a/ui/ts/protocol/oracleRequestFunding.ts +++ b/ui/zoltar/ts/protocol/oracleRequestFunding.ts @@ -1,4 +1,4 @@ -import type { OracleManagerDetails } from '../types/contracts.js' +import type { OracleManagerDetails } from '@zoltar/ui-core-shared/types/contracts.js' export function resolveOracleOperationEthFunding({ managerDetails, priceUsable }: { managerDetails: OracleManagerDetails | undefined; priceUsable?: boolean | undefined }) { if (managerDetails === undefined) return undefined diff --git a/ui/ts/protocol/oracleTiming.ts b/ui/zoltar/ts/protocol/oracleTiming.ts similarity index 100% rename from ui/ts/protocol/oracleTiming.ts rename to ui/zoltar/ts/protocol/oracleTiming.ts diff --git a/ui/ts/protocol/reporting.ts b/ui/zoltar/ts/protocol/reporting.ts similarity index 99% rename from ui/ts/protocol/reporting.ts rename to ui/zoltar/ts/protocol/reporting.ts index 7c42e276e..0835eb427 100644 --- a/ui/ts/protocol/reporting.ts +++ b/ui/zoltar/ts/protocol/reporting.ts @@ -1,12 +1,12 @@ import { bigintToSafeNumber, concatHex, encodeAbiParameters, keccak256, parseAbiParameters, zeroAddress, type Address, type ContractFunctionParameters, type Hex } from '@zoltar/shared/ethereum' -import { Zoltar_Zoltar, statoblast_EscalationGame_EscalationGame, statoblast_SecurityPool_SecurityPool, statoblast_SecurityPoolForker_SecurityPoolForker } from '../contractArtifact.js' -import { sameAddress } from '../lib/address.js' -import type { CarriedDepositProof, EscalationDeposit, EscalationSide, ImportedEscalationDeposit, ReadClient, ReportingActionResult, ReportingDetails, ReportingOutcomeKey, ReportingSettlementState, WriteClient } from '../types/contracts.js' +import { Zoltar_Zoltar, statoblast_EscalationGame_EscalationGame, statoblast_SecurityPool_SecurityPool, statoblast_SecurityPoolForker_SecurityPoolForker } from '@zoltar/ui-core-shared/contractArtifact.js' +import { sameAddress } from '@zoltar/ui-core-shared/lib/address.js' +import type { CarriedDepositProof, EscalationDeposit, EscalationSide, ImportedEscalationDeposit, ReadClient, ReportingActionResult, ReportingDetails, ReportingOutcomeKey, ReportingSettlementState, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' import { readRequiredMulticall, writeContractAndWait } from './core.js' import { requireAddressValue, requireArrayValue, requireBigintValue, requireBooleanValue, requireIntegerLikeValue, requireObjectValue, requireTupleValue } from './decoders.js' import { getInfraContractAddresses } from './deploymentHelpers.js' import { getEscalationSideLabel, getReportingOutcomeKey, getReportingOutcomeValue, getSecurityPoolSystemState, hasTimestamp, requireSecurityVaultTupleArray } from './helpers.js' -import { executeForkAuctionAction, readSecurityPoolUniverseId } from './securityPoolActions.js' +import { executeForkAuctionAction, readSecurityPoolUniverseId } from '@zoltar/ui-core-shared/protocol/securityPoolActions.js' import { loadMarketDetails } from './zoltar.js' const MIGRATION_TIME_LENGTH = 4838400n diff --git a/ui/zoltar/ts/protocol/tokenActions.ts b/ui/zoltar/ts/protocol/tokenActions.ts new file mode 100644 index 000000000..0e87dc19b --- /dev/null +++ b/ui/zoltar/ts/protocol/tokenActions.ts @@ -0,0 +1,14 @@ +import { type Address } from '@zoltar/shared/ethereum' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' +import type { OpenOracleActionResult, WriteClient, ZoltarForkActionResult } from '@zoltar/ui-core-shared/types/contracts.js' +import { writeContractAndWait } from './core.js' + +export async function approveErc20(client: WriteClient, tokenAddress: Address, spenderAddress: Address, amount: bigint, action: Action) { + const hash = await writeContractAndWait(client, () => ({ + address: tokenAddress, + abi: ABIS.mainnet.erc20, + functionName: 'approve', + args: [spenderAddress, amount], + })) + return { action, hash } +} diff --git a/ui/ts/protocol/uniswapQuoter.ts b/ui/zoltar/ts/protocol/uniswapQuoter.ts similarity index 98% rename from ui/ts/protocol/uniswapQuoter.ts rename to ui/zoltar/ts/protocol/uniswapQuoter.ts index 0f9ab1122..fb9e97007 100644 --- a/ui/ts/protocol/uniswapQuoter.ts +++ b/ui/zoltar/ts/protocol/uniswapQuoter.ts @@ -1,8 +1,8 @@ import { encodeAbiParameters, getAddress, keccak256, type Address, type Hex, zeroAddress } from '@zoltar/shared/ethereum' -import type { ReadClient } from '../lib/clients.js' -import { getActiveNetworkProfile, getActiveSimulationController } from '../lib/activeEnvironment.js' -import { isRecoverableContractReadError, isRecoverableQuoteError } from '../lib/errors.js' -import { MAINNET_NETWORK_PROFILE, MAINNET_WETH_ADDRESS } from '../lib/networkProfile.js' +import type { ReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { getActiveNetworkProfile, getActiveSimulationController } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { isRecoverableContractReadError, isRecoverableQuoteError } from '@zoltar/ui-core-shared/lib/errors.js' +import { MAINNET_NETWORK_PROFILE, MAINNET_WETH_ADDRESS } from '@zoltar/ui-core-shared/lib/networkProfile.js' import { getGenesisReputationTokenAddress, getWethAddress } from './activeProtocolAddresses.js' export { getWethAddress } diff --git a/ui/ts/protocol/zoltar.ts b/ui/zoltar/ts/protocol/zoltar.ts similarity index 98% rename from ui/ts/protocol/zoltar.ts rename to ui/zoltar/ts/protocol/zoltar.ts index 08c8cfde9..ff23bc512 100644 --- a/ui/ts/protocol/zoltar.ts +++ b/ui/zoltar/ts/protocol/zoltar.ts @@ -1,6 +1,6 @@ import { zeroAddress, type Address } from '@zoltar/shared/ethereum' -import { ReputationToken_ReputationToken, Zoltar_Zoltar, ZoltarQuestionData_ZoltarQuestionData } from '../contractArtifact.js' -import type { MarketCreationResult, MarketDetails, MarketDetailsPage, MarketType, QuestionData, ReadClient, WriteClient, ZoltarUniverseSummary } from '../types/contracts.js' +import { ReputationToken_ReputationToken, Zoltar_Zoltar, ZoltarQuestionData_ZoltarQuestionData } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { MarketCreationResult, MarketDetails, MarketDetailsPage, MarketType, QuestionData, ReadClient, WriteClient, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' import { readRequiredMulticall, writeContractAndWait } from './core.js' import { getMarketType, getProtocolPageOffset, getQuestionId, getQuestionIdHex, isStringArray, requireDeployedChildUniverseTupleArray, requireUniverseTupleArray, type UniverseTuple } from './helpers.js' import { getDeploymentSteps } from './deployment.js' diff --git a/ui/zoltar/ts/simulation/tevmWorker.ts b/ui/zoltar/ts/simulation/tevmWorker.ts new file mode 100644 index 000000000..6f7ee0e81 --- /dev/null +++ b/ui/zoltar/ts/simulation/tevmWorker.ts @@ -0,0 +1,14 @@ +/// + +import { getDeploymentSteps } from '../protocol/deployment.js' +import { getZoltarAddress } from '../protocol/deploymentHelpers.js' +import type { SimulationEngineDependencies } from '@zoltar/ui-core-shared/simulation/tevmEngine.js' + +const dependencies: SimulationEngineDependencies = { + getDeploymentSteps, + getZoltarAddress, +} + +globalThis.zoltarSimulationEngineDependencies = dependencies + +import '@zoltar/ui-core-shared/simulation/tevmWorker.js' diff --git a/ui/ts/tests/app/appPageTitle.test.tsx b/ui/zoltar/ts/tests/app/appPageTitle.test.tsx similarity index 74% rename from ui/ts/tests/app/appPageTitle.test.tsx rename to ui/zoltar/ts/tests/app/appPageTitle.test.tsx index 284716278..0a7cc261e 100644 --- a/ui/ts/tests/app/appPageTitle.test.tsx +++ b/ui/zoltar/ts/tests/app/appPageTitle.test.tsx @@ -1,17 +1,16 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { within } from '../testUtils/queries' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' import { render } from 'preact' -import { AppPageHeading } from '../../app/components/AppPageHeading.js' +import { AppPageHeading } from '@zoltar/ui-core-shared/app/components/AppPageHeading.js' import { formatAppDocumentTitle, getAppPageTitle, type AppPageTitleInput } from '../../app/lib/appPageTitle.js' -import { installDomEnvironment } from '../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' const baseInput: AppPageTitleInput = { activeOpenOracleView: 'browse', - activeSecurityPoolsView: 'browse', activeZoltarView: 'questions', route: 'zoltar', } @@ -39,9 +38,6 @@ describe('app page titles', () => { { input: { ...baseInput, route: 'zoltar', activeZoltarView: 'create' }, title: 'Create Question' }, { input: { ...baseInput, route: 'zoltar', activeZoltarView: 'fork' }, title: 'Fork Universe' }, { input: { ...baseInput, route: 'zoltar', activeZoltarView: 'migrate' }, title: 'Migrate REP' }, - { input: { ...baseInput, route: 'security-pools', activeSecurityPoolsView: 'browse' }, title: 'Security Pools' }, - { input: { ...baseInput, route: 'security-pools', activeSecurityPoolsView: 'create' }, title: 'Create Security Pool' }, - { input: { ...baseInput, route: 'security-pools', activeSecurityPoolsView: 'operate' }, title: 'Manage Security Pool' }, { input: { ...baseInput, route: 'open-oracle', activeOpenOracleView: 'browse' }, title: 'Open Oracle' }, { input: { ...baseInput, route: 'open-oracle', activeOpenOracleView: 'create' }, title: 'Create Open Oracle Report' }, { input: { ...baseInput, route: 'open-oracle', activeOpenOracleView: 'selected-report' }, title: 'Open Oracle Report Details' }, @@ -54,11 +50,12 @@ describe('app page titles', () => { }) test('renders the hidden page heading and updates the document title', async () => { - const renderedComponent = await renderIntoDocument() + const renderedComponent = await renderIntoDocument() cleanupRenderedComponent = renderedComponent.cleanup - expect(document.title).toBe(formatAppDocumentTitle('Security Pools')) - const heading = within(document.body).getByRole('heading', { level: 1, name: 'Security Pools' }) + expect(document.title).toBe(formatAppDocumentTitle('Questions')) + expect(document.title).toBe('Questions | Zoltar') + const heading = within(document.body).getByRole('heading', { level: 1, name: 'Questions' }) expect(heading.classList.contains('visually-hidden')).toBe(true) }) @@ -72,8 +69,8 @@ describe('app page titles', () => { try { const renderedComponent = await renderIntoDocument( <> - -
Pool content
+ +
Question content
, ) cleanupRenderedComponent = renderedComponent.cleanup @@ -81,14 +78,14 @@ describe('app page titles', () => { await act(() => { render( <> - -
Create pool content
+ +
Create question content
, renderedComponent.container, ) }) - const heading = within(document.body).getByRole('heading', { level: 1, name: 'Create Security Pool' }) + const heading = within(document.body).getByRole('heading', { level: 1, name: 'Create Question' }) expect(document.activeElement).toBe(heading) expect(heading.getAttribute('tabindex')).toBe('-1') expect(scrollIntoViewCalls).toBe(1) @@ -105,22 +102,22 @@ describe('app page titles', () => { } try { - window.history.replaceState({}, '', '#/security-pools?selectedPoolView=vaults') + window.history.replaceState({}, '', '#/zoltar?view=questions') const renderedComponent = await renderIntoDocument( <> - -
Pool content
+ +
Question content
, ) cleanupRenderedComponent = renderedComponent.cleanup - window.history.pushState({}, '', '#/security-pools?selectedPoolView=reporting') + window.history.pushState({}, '', '#/zoltar?view=create') window.dispatchEvent(new Event('popstate')) await act(() => { render( <> - -
Reporting content
+ +
Create content
, renderedComponent.container, ) @@ -130,7 +127,7 @@ describe('app page titles', () => { await act(() => { render( <> - +
Oracle content
, renderedComponent.container, diff --git a/ui/ts/tests/app/appRouteContent.test.ts b/ui/zoltar/ts/tests/app/appRouteContent.test.ts similarity index 71% rename from ui/ts/tests/app/appRouteContent.test.ts rename to ui/zoltar/ts/tests/app/appRouteContent.test.ts index 0991e6f81..715f104ec 100644 --- a/ui/ts/tests/app/appRouteContent.test.ts +++ b/ui/zoltar/ts/tests/app/appRouteContent.test.ts @@ -2,8 +2,16 @@ import { describe, expect, test } from 'bun:test' import { shouldRenderRouteContent } from '../../app/components/AppRouteContent.js' +import { MAINNET_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { onchainStateDependencies } from '../../app/onchainStateDependencies.js' +import { getDeploymentSteps } from '../../protocol/deployment.js' describe('AppRouteContent', () => { + test('injects the Zoltar-specific deployment plan into shared onchain state', () => { + expect(onchainStateDependencies.getDeploymentSteps).toBe(getDeploymentSteps) + expect(onchainStateDependencies.getDeploymentSteps(MAINNET_NETWORK_PROFILE).some(step => step.id === 'securityPoolFactory')).toBe(false) + }) + test('keeps route content visible when the read backend is ready', () => { expect(shouldRenderRouteContent({ readBackendMessage: undefined, route: 'zoltar' })).toBe(true) expect(shouldRenderRouteContent({ readBackendMessage: undefined, route: 'security-pools' })).toBe(true) diff --git a/ui/ts/tests/app/globalTransactionTray.test.tsx b/ui/zoltar/ts/tests/app/globalTransactionTray.test.tsx similarity index 93% rename from ui/ts/tests/app/globalTransactionTray.test.tsx rename to ui/zoltar/ts/tests/app/globalTransactionTray.test.tsx index 1616d56e6..be4e0f799 100644 --- a/ui/ts/tests/app/globalTransactionTray.test.tsx +++ b/ui/zoltar/ts/tests/app/globalTransactionTray.test.tsx @@ -1,22 +1,25 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' import { render } from 'preact' -import { GlobalTransactionTray } from '../../app/components/GlobalTransactionTray.js' -import { createMarketCreationSuccessPresentation, createSecurityPoolCreationSuccessPresentation, createTradingSuccessPresentation, createTradingTransactionIntent, createZoltarForkSuccessPresentation } from '../../features/transactionPresentations.js' -import { installDomEnvironment } from '../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' -import { createInitialTransactionTrayState, markTransactionFailed, markTransactionPresented, markTransactionRequested, markTransactionSubmitted } from '../../lib/transactionTray.js' +import { GlobalTransactionTray } from '@zoltar/ui-core-shared/app/components/GlobalTransactionTray.js' +import { createMarketCreationSuccessPresentation, createMarketCreationTransactionIntent, createZoltarForkSuccessPresentation } from '../../features/transactionPresentations.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' +import { createInitialTransactionTrayState, markTransactionFailed, markTransactionPresented, markTransactionRequested, markTransactionSubmitted } from '@zoltar/ui-core-shared/lib/transactionTray.js' describe('GlobalTransactionTray', () => { let restoreDomEnvironment: (() => void) | undefined + let restoreRouting: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined beforeEach(() => { const domEnvironment = installDomEnvironment() restoreDomEnvironment = domEnvironment.cleanup + restoreRouting = installTestRouting() }) afterEach(async () => { @@ -24,6 +27,8 @@ describe('GlobalTransactionTray', () => { cleanupRenderedComponent = undefined restoreDomEnvironment?.() restoreDomEnvironment = undefined + restoreRouting?.() + restoreRouting = undefined }) test('does not render when there is no submitted transaction', async () => { @@ -58,12 +63,11 @@ describe('GlobalTransactionTray', () => { }) test('warns when transaction lifecycle state belongs to a different header universe', async () => { - const securityPoolAddress = '0x0000000000000000000000000000000000000001' - const intent = createTradingTransactionIntent('createCompleteSet', { securityPoolAddress, universeId: 7n }) + const intent = createMarketCreationTransactionIntent({ marketType: 'binary', universeId: 7n }) const requested = markTransactionRequested(createInitialTransactionTrayState(), intent) const submitted = markTransactionSubmitted(requested, '0xb234000000000000000000000000000000000000000000000000000000000000') const failed = markTransactionFailed(submitted, 'Transaction reverted') - const success = createTradingSuccessPresentation({ action: 'createCompleteSet', hash: '0xb234000000000000000000000000000000000000000000000000000000000000', securityPoolAddress, universeId: 7n }) + const success = createMarketCreationSuccessPresentation({ createQuestionHash: '0xb234000000000000000000000000000000000000000000000000000000000000', marketType: 'binary', questionId: '0x01' }, { universeId: 7n }) const lifecyclePresentations = [requested.active, submitted.active, failed.active, success] if (lifecyclePresentations.some(presentation => presentation === undefined)) throw new Error('Transaction lifecycle presentation should be defined') @@ -128,11 +132,7 @@ describe('GlobalTransactionTray', () => { test('renders complete copyable question identifiers across success notices', async () => { const questionId = '0x0000000000000000000000000000000000000000000000000000000000000001' - const presentations = [ - createMarketCreationSuccessPresentation({ createQuestionHash: '0x1001', marketType: 'binary', questionId }), - createZoltarForkSuccessPresentation({ action: 'forkZoltar', hash: '0x1002', questionId, universeId: 0n }), - createSecurityPoolCreationSuccessPresentation({ deployPoolHash: '0x1003', initialReportPriorityFeeAttoEthPerGas: 10_000_000_000n, questionId, statoblastSecurityMultiplierBps: 20_000n, securityPoolAddress: '0x0000000000000000000000000000000000000002', universeId: 0n }), - ] + const presentations = [createMarketCreationSuccessPresentation({ createQuestionHash: '0x1001', marketType: 'binary', questionId }), createZoltarForkSuccessPresentation({ action: 'forkZoltar', hash: '0x1002', questionId, universeId: 0n })] const renderedComponent = await renderIntoDocument( <> {presentations.map(presentation => ( @@ -143,7 +143,7 @@ describe('GlobalTransactionTray', () => { cleanupRenderedComponent = renderedComponent.cleanup const identifierButtons = within(document.body).getAllByRole('button', { name: `Copy identifier ${questionId}` }) - expect(identifierButtons).toHaveLength(3) + expect(identifierButtons).toHaveLength(2) for (const identifierButton of identifierButtons) expect(identifierButton.textContent).toBe(questionId) }) diff --git a/ui/ts/tests/app/overviewPanels.test.tsx b/ui/zoltar/ts/tests/app/overviewPanels.test.tsx similarity index 92% rename from ui/ts/tests/app/overviewPanels.test.tsx rename to ui/zoltar/ts/tests/app/overviewPanels.test.tsx index 77d8923b9..06f6cb9fa 100644 --- a/ui/ts/tests/app/overviewPanels.test.tsx +++ b/ui/zoltar/ts/tests/app/overviewPanels.test.tsx @@ -1,15 +1,17 @@ /// import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, within } from '../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' import { OverviewPanels } from '../../app/components/OverviewPanels.js' -import { installDomEnvironment } from '../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { act } from 'preact/test-utils' -import { installActiveEnvironmentForTesting } from '../../lib/activeEnvironment.js' -import { SEPOLIA_NETWORK_PROFILE } from '../../lib/networkProfile.js' -import { createFakeBackend } from '../testUtils/fakeBackend.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { SEPOLIA_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +installTestRouting() describe('OverviewPanels', () => { type MetricElement = { classList: { @@ -28,6 +30,8 @@ describe('OverviewPanels', () => { async function renderOverviewPanels(overrides: Partial[0]> = {}) { const baseProps: Parameters[0] = { + applicationTitle: 'Zoltar', + activeUniverseId: 0n, accountState: { address: undefined, chainId: '0x1', @@ -145,6 +149,18 @@ describe('OverviewPanels', () => { expect(connectButton.disabled).toBe(false) }) + test('identifies the Zoltar application in its operations header', async () => { + const documentQueries = await renderOverviewPanels() + expect(documentQueries.getByRole('heading', { level: 2, name: 'Zoltar' })).toBeDefined() + expect(document.body.textContent).not.toContain('Augur Statoblast') + }) + + test('uses the application-owned title supplied by a dependent app', async () => { + const documentQueries = await renderOverviewPanels({ applicationTitle: 'Augur Statoblast' }) + expect(documentQueries.getByRole('heading', { level: 2, name: 'Augur Statoblast' })).toBeDefined() + expect(document.body.textContent).not.toContain('Zoltar') + }) + test('keeps the active Sepolia deployment target visible while disconnected', async () => { const resetEnvironment = installActiveEnvironmentForTesting(createFakeBackend({ profile: SEPOLIA_NETWORK_PROFILE })) try { diff --git a/ui/ts/tests/copy.test.ts b/ui/zoltar/ts/tests/copy.test.ts similarity index 97% rename from ui/ts/tests/copy.test.ts rename to ui/zoltar/ts/tests/copy.test.ts index 416fb45b7..b0ed12e8e 100644 --- a/ui/ts/tests/copy.test.ts +++ b/ui/zoltar/ts/tests/copy.test.ts @@ -1,13 +1,13 @@ import { expect, test } from 'bun:test' import * as forkAuctionCopy from '../copy/forkAuction.js' -import * as appCopy from '../copy/app.js' -import * as commonCopy from '../copy/common.js' +import * as appCopy from '@zoltar/ui-core-shared/copy/app.js' +import * as commonCopy from '@zoltar/ui-core-shared/copy/common.js' import * as marketCopy from '../copy/market.js' import * as liquidationCopy from '../copy/liquidation.js' import * as openOracleCopy from '../copy/openOracle.js' import * as reportingCopy from '../copy/reporting.js' import * as securityPoolCopy from '../copy/securityPool.js' -import * as simulationCopy from '../copy/simulation.js' +import * as simulationCopy from '@zoltar/ui-core-shared/copy/simulation.js' import * as tradingCopy from '../copy/trading.js' import * as zoltarCopy from '../copy/zoltar.js' diff --git a/ui/ts/tests/features/deployment/deployment.test.ts b/ui/zoltar/ts/tests/features/deployment/deployment.test.ts similarity index 85% rename from ui/ts/tests/features/deployment/deployment.test.ts rename to ui/zoltar/ts/tests/features/deployment/deployment.test.ts index 4a44f5671..91447a032 100644 --- a/ui/ts/tests/features/deployment/deployment.test.ts +++ b/ui/zoltar/ts/tests/features/deployment/deployment.test.ts @@ -3,27 +3,27 @@ import { beforeEach, describe, expect, setDefaultTimeout, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { findNextDeployableStep, getDeploymentSections, getDeploymentStepAvailability, getDeployNextMissingAvailability, getPrerequisiteLabel } from '../../../features/deployment/lib/deployment.js' -import { createConnectedReadClient } from '../../../lib/clients.js' -import type { InjectedEthereum } from '../../../injectedEthereum.js' +import { createConnectedReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import type { InjectedEthereum } from '@zoltar/ui-core-shared/injectedEthereum.js' import { getDeploymentSteps, getMulticall3Address, getOpenOracleAddress, loadDeploymentStatusOracleSnapshot, loadZoltarUniverseSummary } from '../../../protocol/index.js' import { OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE } from '../../../protocol/deploymentHelpers.js' -import type { DeploymentStatus, ReadClient } from '../../../types/contracts.js' -import { AnvilWindowEthereum } from '../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' -import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' -import { createWriteClient, type WriteClient as SolidityWriteClient } from '../../../../../solidity/ts/testSupport/simulator/utils/clients' -import { TEST_ADDRESSES } from '../../../../../solidity/ts/testSupport/simulator/utils/constants' +import type { DeploymentStatus, ReadClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { AnvilWindowEthereum } from '../../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' +import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' +import { createWriteClient, type WriteClient as SolidityWriteClient } from '../../../../../../solidity/ts/testSupport/simulator/utils/clients' +import { TEST_ADDRESSES } from '../../../../../../solidity/ts/testSupport/simulator/utils/constants' import { ORACLE_GAS_UNITS_FOR_ONE_DISPUTE as SIMULATOR_ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, OPEN_ORACLE_SECURITY_MULTIPLIER_BPS as SIMULATOR_OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE as SIMULATOR_ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE, -} from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' -import { ensureProxyDeployerDeployed, setupTestAccounts } from '../../../../../solidity/ts/testSupport/simulator/utils/utilities' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { SEPOLIA_NETWORK_PROFILE } from '../../../lib/networkProfile.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' +} from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' +import { ensureProxyDeployerDeployed, setupTestAccounts } from '../../../../../../solidity/ts/testSupport/simulator/utils/utilities' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { SEPOLIA_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' import { SEPOLIA_REP_ALLOCATIONS, SEPOLIA_REP_TOTAL_THEORETICAL_SUPPLY } from '@zoltar/shared/sepoliaRepAllocations' -import type { WriteClient as UiWriteClient } from '../../../types/contracts.js' -import { GenesisReputationToken_GenesisReputationToken, Zoltar_Zoltar } from '../../../contractArtifact.js' +import type { WriteClient as UiWriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { GenesisReputationToken_GenesisReputationToken, Zoltar_Zoltar } from '@zoltar/ui-core-shared/contractArtifact.js' const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' @@ -126,23 +126,7 @@ void describe('deployment helpers', () => { const deploymentSteps = getDeploymentSteps() const deploymentStatusOracleStep = deploymentSteps.find(step => step.id === 'deploymentStatusOracle') - expect(deploymentSteps.map(step => step.id)).toEqual([ - 'proxyDeployer', - 'deploymentStatusOracle', - 'multicall3', - 'uniformPriceDualCapBatchAuctionFactory', - 'scalarOutcomes', - 'securityPoolUtils', - 'openOracle', - 'zoltarQuestionData', - 'zoltar', - 'shareTokenFactory', - 'priceOracleManagerAndOperatorQueuerFactory', - 'securityPoolForker', - 'escalationGameClaimDelegate', - 'escalationGameFactory', - 'securityPoolFactory', - ]) + expect(deploymentSteps.map(step => step.id)).toEqual(['proxyDeployer', 'deploymentStatusOracle', 'multicall3', 'uniformPriceDualCapBatchAuctionFactory', 'scalarOutcomes', 'securityPoolUtils', 'openOracle', 'zoltarQuestionData', 'zoltar', 'shareTokenFactory', 'priceOracleManagerAndOperatorQueuerFactory']) expect(deploymentStatusOracleStep?.dependencies).toEqual(['proxyDeployer']) expect(deploymentStatusOracleStep?.label).toBe('Deployment Status Oracle') }) @@ -226,7 +210,7 @@ void describe('deployment helpers', () => { void test('loadDeploymentStatusOracleSnapshot returns the proxy deployer when the oracle is missing', async () => { const snapshot = await loadDeploymentStatusOracleSnapshot(readClient) - expect(snapshot.augurStatoblastDeployed).toBe(false) + expect(snapshot.applicationDeploymentComplete).toBe(false) expect(snapshot.deploymentStatuses.find(step => step.id === 'proxyDeployer')?.deployed).toBe(true) expect(snapshot.deploymentStatuses.find(step => step.id === 'deploymentStatusOracle')?.deployed).toBe(false) }) diff --git a/ui/ts/tests/features/deployment/deploymentRouteContent.test.tsx b/ui/zoltar/ts/tests/features/deployment/deploymentRouteContent.test.tsx similarity index 93% rename from ui/ts/tests/features/deployment/deploymentRouteContent.test.tsx rename to ui/zoltar/ts/tests/features/deployment/deploymentRouteContent.test.tsx index bb615272a..690aaa11e 100644 --- a/ui/ts/tests/features/deployment/deploymentRouteContent.test.tsx +++ b/ui/zoltar/ts/tests/features/deployment/deploymentRouteContent.test.tsx @@ -1,15 +1,16 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' import { h } from 'preact' import { zeroAddress } from '@zoltar/shared/ethereum' import { DeploymentRouteContent } from '../../../features/deployment/components/DeploymentRouteContent.js' import type { DeploymentRouteContentProps } from '../../../features/types.js' -import type { DeploymentStatus } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' +import type { DeploymentStatus } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' function createStep(id: DeploymentStatus['id'], label: string, deployed: boolean, dependencies: DeploymentStatus['id'][] = []): DeploymentStatus { return { @@ -44,6 +45,7 @@ function createProps(): DeploymentRouteContentProps { } } +installTestRouting() describe('DeploymentRouteContent', () => { let restoreDomEnvironment: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/deployment/deploymentSection.test.tsx b/ui/zoltar/ts/tests/features/deployment/deploymentSection.test.tsx similarity index 94% rename from ui/ts/tests/features/deployment/deploymentSection.test.tsx rename to ui/zoltar/ts/tests/features/deployment/deploymentSection.test.tsx index f2ff3bbbb..c87b36ca0 100644 --- a/ui/ts/tests/features/deployment/deploymentSection.test.tsx +++ b/ui/zoltar/ts/tests/features/deployment/deploymentSection.test.tsx @@ -3,10 +3,10 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { DeploymentSection } from '../../../features/deployment/components/DeploymentSection.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import type { DeploymentStatus } from '../../../types/contracts.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import type { DeploymentStatus } from '@zoltar/ui-core-shared/types/contracts.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' const ZERO_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000' diff --git a/ui/ts/tests/features/deployment/useDeploymentFlow.test.tsx b/ui/zoltar/ts/tests/features/deployment/useDeploymentFlow.test.tsx similarity index 96% rename from ui/ts/tests/features/deployment/useDeploymentFlow.test.tsx rename to ui/zoltar/ts/tests/features/deployment/useDeploymentFlow.test.tsx index 0e66cb31a..69e4725ba 100644 --- a/ui/ts/tests/features/deployment/useDeploymentFlow.test.tsx +++ b/ui/zoltar/ts/tests/features/deployment/useDeploymentFlow.test.tsx @@ -4,13 +4,13 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h } from 'preact' import { act } from 'preact/test-utils' import { createWalletClient, custom, getAddress, keccak256, publicActions, type Hash } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' import { useDeploymentFlow } from '../../../features/deployment/hooks/useDeploymentFlow.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import type { DeploymentStatus } from '../../../types/contracts.js' -import { MAINNET_NETWORK_PROFILE } from '../../../lib/networkProfile.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import type { DeploymentStatus } from '@zoltar/ui-core-shared/types/contracts.js' +import { MAINNET_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' type UseDeploymentFlowState = ReturnType diff --git a/ui/ts/tests/features/open-oracle/openOracle.test.ts b/ui/zoltar/ts/tests/features/open-oracle/openOracle.test.ts similarity index 97% rename from ui/ts/tests/features/open-oracle/openOracle.test.ts rename to ui/zoltar/ts/tests/features/open-oracle/openOracle.test.ts index e403ec0ba..1b162eebf 100644 --- a/ui/ts/tests/features/open-oracle/openOracle.test.ts +++ b/ui/zoltar/ts/tests/features/open-oracle/openOracle.test.ts @@ -36,23 +36,24 @@ import { parseOpenOracleFeePercentageInput, } from '../../../features/open-oracle/lib/openOracle.js' import { loadOpenOracleInitialReportPrice, loadOpenOracleInitialReportPriceResult } from '../../../protocol/openOraclePricing.js' -import { getDefaultOpenOracleCreateFormState } from '../../../features/markets/lib/marketForm.js' -import { ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS } from '../../../features/security-pools/lib/securityVault.js' -import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' +import { getDefaultOpenOracleCreateFormState } from '../../../lib/formDefaults.js' +import { ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS } from '../../../protocol/oracleTiming.js' +import { createConnectedReadClient, createWalletWriteClient } from '@zoltar/ui-core-shared/lib/clients.js' import { ETH_ADDRESS, REP_ADDRESS, UNISWAP_V4_QUOTER_ADDRESS, USDC_ADDRESS } from '../../../protocol/uniswapQuoter.js' -import { statoblast_openOracle_OpenOracle_OpenOracle } from '../../../contractArtifact.js' -import type { InjectedEthereum } from '../../../injectedEthereum.js' +import { resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { statoblast_openOracle_OpenOracle_OpenOracle } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { InjectedEthereum } from '@zoltar/ui-core-shared/injectedEthereum.js' import type { WriteContractClient } from '../../../protocol/core.js' -import { DAY, GENESIS_REPUTATION_TOKEN, WETH_ADDRESS, TEST_ADDRESSES } from '../../../../../solidity/ts/testSupport/simulator/utils/constants' -import { addressString } from '../../../../../solidity/ts/testSupport/simulator/utils/bigint' -import { setupTestAccounts, ensureProxyDeployerDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/utilities' -import { AnvilWindowEthereum } from '../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' -import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' -import { createWriteClient, type WriteClient } from '../../../../../solidity/ts/testSupport/simulator/utils/clients' -import { deployOriginSecurityPool, ensureInfraDeployed, getSecurityPoolAddresses } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' -import { ensureZoltarDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' -import { createQuestion, getQuestionId } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltarQuestionData' -import { getOpenOracleExtraData, getRequestPriceCostAttoEth, requestPriceWithValue } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/statoblast' +import { DAY, GENESIS_REPUTATION_TOKEN, WETH_ADDRESS, TEST_ADDRESSES } from '../../../../../../solidity/ts/testSupport/simulator/utils/constants' +import { addressString } from '../../../../../../solidity/ts/testSupport/simulator/utils/bigint' +import { setupTestAccounts, ensureProxyDeployerDeployed } from '../../../../../../solidity/ts/testSupport/simulator/utils/utilities' +import { AnvilWindowEthereum } from '../../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' +import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' +import { createWriteClient, type WriteClient } from '../../../../../../solidity/ts/testSupport/simulator/utils/clients' +import { deployOriginSecurityPool, ensureInfraDeployed, getSecurityPoolAddresses } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' +import { ensureZoltarDeployed } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' +import { createQuestion, getQuestionId } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltarQuestionData' +import { getOpenOracleExtraData, getRequestPriceCostAttoEth, requestPriceWithValue } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/statoblast' setDefaultTimeout(TEST_TIMEOUT_MS) @@ -215,6 +216,7 @@ describe('Open Oracle helpers', () => { }) beforeEach(() => { + resetActiveEnvironmentForTesting() mockWindow = getAnvilWindowEthereum() client = createWriteClient(mockWindow, TEST_ADDRESSES[0], 0) installInjectedEthereum(mockWindow) diff --git a/ui/ts/tests/features/open-oracle/openOracleReadiness.test.ts b/ui/zoltar/ts/tests/features/open-oracle/openOracleReadiness.test.ts similarity index 100% rename from ui/ts/tests/features/open-oracle/openOracleReadiness.test.ts rename to ui/zoltar/ts/tests/features/open-oracle/openOracleReadiness.test.ts diff --git a/ui/ts/tests/features/open-oracle/openOracleRouteSection.test.tsx b/ui/zoltar/ts/tests/features/open-oracle/openOracleRouteSection.test.tsx similarity index 98% rename from ui/ts/tests/features/open-oracle/openOracleRouteSection.test.tsx rename to ui/zoltar/ts/tests/features/open-oracle/openOracleRouteSection.test.tsx index 1e4a0a648..0bad12f7f 100644 --- a/ui/ts/tests/features/open-oracle/openOracleRouteSection.test.tsx +++ b/ui/zoltar/ts/tests/features/open-oracle/openOracleRouteSection.test.tsx @@ -1,22 +1,22 @@ /// import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { h, render } from 'preact' import { act } from 'preact/test-utils' import { useState } from 'preact/hooks' import { zeroAddress } from '@zoltar/shared/ethereum' import { OpenOracleSection } from '../../../features/open-oracle/components/OpenOracleSection.js' import * as openOracleCopy from '../../../copy/openOracle.js' -import { ChainBlockNumberContext, ChainTimestampContext } from '../../../lib/chainTimestamp.js' -import { getDefaultOpenOracleCreateFormState, getDefaultOpenOracleFormState } from '../../../features/markets/lib/marketForm.js' +import { ChainBlockNumberContext, ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { getDefaultOpenOracleCreateFormState, getDefaultOpenOracleFormState } from '../../../lib/formDefaults.js' import { deriveOpenOracleDisputeSubmissionDetails } from '../../../features/open-oracle/lib/openOracle.js' import type { AccountState, OpenOracleCreateFormState } from '../../../types/app.js' import type { OpenOracleSectionProps } from '../../../features/types.js' -import type { OpenOracleReportDetails } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' +import type { OpenOracleReportDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' const ATTO_ETH_PER_ETH = 10n ** 18n @@ -545,7 +545,7 @@ describe('OpenOracleSection route create view', () => { ) cleanupRenderedComponent = renderedComponent.cleanup - expectTransactionButtonDisabled(document.body, 'Create standalone Oracle report', 'Need 100 more ETH in this wallet to create the selected standalone Open Oracle report.') + expectTransactionButtonDisabled(document.body, 'Create standalone Oracle report', 'Need 100\u00a0more\u00a0ETH in this wallet to create the selected standalone Open Oracle report.') }) test('does not disable create before token decimals are loaded for large but valid token1 amounts', async () => { diff --git a/ui/ts/tests/features/open-oracle/openOracleSection.integration.test.tsx b/ui/zoltar/ts/tests/features/open-oracle/openOracleSection.integration.test.tsx similarity index 91% rename from ui/ts/tests/features/open-oracle/openOracleSection.integration.test.tsx rename to ui/zoltar/ts/tests/features/open-oracle/openOracleSection.integration.test.tsx index ffc4fc0db..fab823c07 100644 --- a/ui/ts/tests/features/open-oracle/openOracleSection.integration.test.tsx +++ b/ui/zoltar/ts/tests/features/open-oracle/openOracleSection.integration.test.tsx @@ -1,33 +1,33 @@ /// import { afterEach, beforeAll, beforeEach, describe, expect, setDefaultTimeout, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { useState } from 'preact/hooks' import { act } from 'preact/test-utils' import type { Address, Hash } from '@zoltar/shared/ethereum' import { getAddress } from '@zoltar/shared/ethereum' import { OpenOracleSection } from '../../../features/open-oracle/components/OpenOracleSection.js' -import { ViewTabs } from '../../../components/ViewTabs.js' +import { ViewTabs } from '@zoltar/ui-core-shared/components/ViewTabs.js' import { createOpenOracleReportInstance, getOpenOracleAddress, loadOpenOracleReportDetails } from '../../../protocol/index.js' import { useOpenOracleOperations } from '../../../features/open-oracle/hooks/useOpenOracleOperations.js' import type { AccountState } from '../../../types/app.js' -import type { InjectedEthereum } from '../../../injectedEthereum.js' -import { createInjectedBackend } from '../../../lib/chainBackend.js' -import { createConnectedReadClient } from '../../../lib/clients.js' +import type { InjectedEthereum } from '@zoltar/ui-core-shared/injectedEthereum.js' +import { createInjectedBackend } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import { createConnectedReadClient } from '@zoltar/ui-core-shared/lib/clients.js' import { formatOpenOracleFeePercentageInput, getOpenOracleSelectedReportActionMode } from '../../../features/open-oracle/lib/openOracle.js' import type { OpenOracleView } from '../../../features/types.js' -import { GENESIS_REPUTATION_TOKEN, TEST_ADDRESSES, WETH_ADDRESS } from '../../../../../solidity/ts/testSupport/simulator/utils/constants' -import { addressString } from '../../../../../solidity/ts/testSupport/simulator/utils/bigint' -import { setupTestAccounts, ensureProxyDeployerDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/utilities' -import { AnvilWindowEthereum } from '../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' -import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' -import { createWriteClient, type WriteClient } from '../../../../../solidity/ts/testSupport/simulator/utils/clients' -import { ensureInfraDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' -import { ensureZoltarDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { formatCurrencyInputBalance } from '../../../lib/formatters.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { GENESIS_REPUTATION_TOKEN, TEST_ADDRESSES, WETH_ADDRESS } from '../../../../../../solidity/ts/testSupport/simulator/utils/constants' +import { addressString } from '../../../../../../solidity/ts/testSupport/simulator/utils/bigint' +import { setupTestAccounts, ensureProxyDeployerDeployed } from '../../../../../../solidity/ts/testSupport/simulator/utils/utilities' +import { AnvilWindowEthereum } from '../../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' +import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' +import { createWriteClient, type WriteClient } from '../../../../../../solidity/ts/testSupport/simulator/utils/clients' +import { ensureInfraDeployed } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployStatoblast' +import { ensureZoltarDeployed } from '../../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { formatCurrencyInputBalance } from '@zoltar/ui-core-shared/lib/formatters.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' setDefaultTimeout(TEST_TIMEOUT_MS) diff --git a/ui/ts/tests/features/open-oracle/openOracleSection.test.tsx b/ui/zoltar/ts/tests/features/open-oracle/openOracleSection.test.tsx similarity index 97% rename from ui/ts/tests/features/open-oracle/openOracleSection.test.tsx rename to ui/zoltar/ts/tests/features/open-oracle/openOracleSection.test.tsx index 1a4ec5573..cd6fc276b 100644 --- a/ui/ts/tests/features/open-oracle/openOracleSection.test.tsx +++ b/ui/zoltar/ts/tests/features/open-oracle/openOracleSection.test.tsx @@ -5,20 +5,20 @@ import { getAddress, zeroAddress } from '@zoltar/shared/ethereum' import { render } from 'preact' import { act } from 'preact/test-utils' import { renderSelectedReportActionSection } from '../../../features/open-oracle/components/OpenOracleSection.js' -import { SectionBlock } from '../../../components/SectionBlock.js' -import { TransactionActionButton } from '../../../components/TransactionActionButton.js' +import { SectionBlock } from '@zoltar/ui-core-shared/components/SectionBlock.js' +import { TransactionActionButton } from '@zoltar/ui-core-shared/components/TransactionActionButton.js' import { deriveOpenOracleDisputeSubmissionDetails, type OpenOracleDisputeSubmissionDetails } from '../../../features/open-oracle/lib/openOracle.js' -import { getDefaultOpenOracleFormState } from '../../../features/markets/lib/marketForm.js' +import { getDefaultOpenOracleFormState } from '../../../lib/formDefaults.js' import type { AccountState, OpenOracleFormState } from '../../../types/app.js' import type { OpenOracleSectionProps } from '../../../features/types.js' -import type { OpenOracleReportDetails, OpenOracleReportSummaryPage } from '../../../types/contracts.js' +import type { OpenOracleReportDetails, OpenOracleReportSummaryPage } from '@zoltar/ui-core-shared/types/contracts.js' import { OpenOracleSection } from '../../../features/open-oracle/components/OpenOracleSection.js' -import { getDefaultOpenOracleCreateFormState } from '../../../features/markets/lib/marketForm.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { fireEvent, within } from '../../testUtils/queries.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { getDefaultOpenOracleCreateFormState } from '../../../lib/formDefaults.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' type VNodeLike = { props: Record diff --git a/ui/ts/tests/features/open-oracle/openOracleStage.test.ts b/ui/zoltar/ts/tests/features/open-oracle/openOracleStage.test.ts similarity index 100% rename from ui/ts/tests/features/open-oracle/openOracleStage.test.ts rename to ui/zoltar/ts/tests/features/open-oracle/openOracleStage.test.ts diff --git a/ui/ts/tests/features/open-oracle/oracleRequestEth.test.ts b/ui/zoltar/ts/tests/features/open-oracle/oracleRequestEth.test.ts similarity index 97% rename from ui/ts/tests/features/open-oracle/oracleRequestEth.test.ts rename to ui/zoltar/ts/tests/features/open-oracle/oracleRequestEth.test.ts index 89bd0c316..773a24773 100644 --- a/ui/ts/tests/features/open-oracle/oracleRequestEth.test.ts +++ b/ui/zoltar/ts/tests/features/open-oracle/oracleRequestEth.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { getOracleRequestEthGuardMessage, resolveOracleOperationEthFunding } from '../../../features/open-oracle/lib/oracleRequestEth.js' -import type { OracleManagerDetails } from '../../../types/contracts.js' +import type { OracleManagerDetails } from '@zoltar/ui-core-shared/types/contracts.js' function createOracleManagerDetails(overrides: Partial = {}): OracleManagerDetails { return { diff --git a/ui/ts/tests/features/open-oracle/useOpenOracleOperations.test.tsx b/ui/zoltar/ts/tests/features/open-oracle/useOpenOracleOperations.test.tsx similarity index 99% rename from ui/ts/tests/features/open-oracle/useOpenOracleOperations.test.tsx rename to ui/zoltar/ts/tests/features/open-oracle/useOpenOracleOperations.test.tsx index f9d32f970..a7b019c01 100644 --- a/ui/ts/tests/features/open-oracle/useOpenOracleOperations.test.tsx +++ b/ui/zoltar/ts/tests/features/open-oracle/useOpenOracleOperations.test.tsx @@ -1,15 +1,15 @@ /// -import { waitFor } from '../../testUtils/queries' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h, render } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import type { OpenOracleReportDetails } from '../../../types/contracts.js' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import type { OpenOracleReportDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { useOpenOracleOperations, type UseOpenOracleOperationsDependencies } from '../../../features/open-oracle/hooks/useOpenOracleOperations.js' import { createOpenOracleReportMissingError } from '../../../protocol/index.js' diff --git a/ui/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx b/ui/zoltar/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx similarity index 78% rename from ui/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx rename to ui/zoltar/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx index a9499b1e2..0d5b78a05 100644 --- a/ui/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx +++ b/ui/zoltar/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx @@ -5,11 +5,11 @@ import { getAddress, zeroAddress } from '@zoltar/shared/ethereum' import { h } from 'preact' import { act } from 'preact/test-utils' import { usePriceOracleManager, type UsePriceOracleManagerDependencies } from '../../../features/open-oracle/hooks/usePriceOracleManager.js' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { createOracleManagerDetails } from '../security-pools/workflow/builders.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import type { OracleManagerDetails } from '@zoltar/ui-core-shared/types/contracts.js' type TestWriteClient = { kind: 'price-oracle-write-client' } type UsePriceOracleManagerState = ReturnType @@ -19,6 +19,29 @@ const POOL_ADDRESS = getAddress('0x00000000000000000000000000000000000000a2') const WALLET_ADDRESS = getAddress('0x00000000000000000000000000000000000000a3') const TRANSACTION_HASH = '0x00000000000000000000000000000000000000000000000000000000000000a4' as const +function createOracleManagerDetails(overrides: Partial = {}): OracleManagerDetails { + return { + callbackStateHash: undefined, + exactToken1Report: undefined, + isPriceValid: true, + lastPrice: 1n, + lastSettlementTimestamp: 1n, + managerAddress: zeroAddress, + openOracleAddress: zeroAddress, + pendingOperation: undefined, + pendingOperationSlotId: 0n, + pendingSettlementOperationIds: [], + pendingSettlementQueueCapacity: 4n, + pendingReportId: 0n, + priceValidUntilTimestamp: 1000n, + queuedOperationCostAttoEth: 1n, + requestPriceCostAttoEth: 1n, + token1: zeroAddress, + token2: zeroAddress, + ...overrides, + } +} + function requireHookState(state: UsePriceOracleManagerState | undefined) { if (state === undefined) throw new Error('Hook state unavailable') return state diff --git a/ui/ts/tests/features/open-oracle/useRepPrices.refresh.test.tsx b/ui/zoltar/ts/tests/features/open-oracle/useRepPrices.refresh.test.tsx similarity index 91% rename from ui/ts/tests/features/open-oracle/useRepPrices.refresh.test.tsx rename to ui/zoltar/ts/tests/features/open-oracle/useRepPrices.refresh.test.tsx index 2af4e0f9f..0a6d8577d 100644 --- a/ui/ts/tests/features/open-oracle/useRepPrices.refresh.test.tsx +++ b/ui/zoltar/ts/tests/features/open-oracle/useRepPrices.refresh.test.tsx @@ -4,11 +4,12 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h } from 'preact' import { act } from 'preact/test-utils' import { createPublicClient, getAddress, http } from '@zoltar/shared/ethereum' -import { fireEvent, waitFor, within } from '../../testUtils/queries' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { installRepPriceQuoterForTesting } from '../../../features/open-oracle/hooks/useRepPrices.js' type UseRepPrices = typeof import('../../../features/open-oracle/hooks/useRepPrices.js')['useRepPrices'] @@ -55,6 +56,7 @@ describe('useRepPrices refresh races', () => { restoreDomEnvironment?.() restoreDomEnvironment = undefined mock.restore() + installRepPriceQuoterForTesting(undefined) resetActiveEnvironmentForTesting() }) @@ -67,8 +69,7 @@ describe('useRepPrices refresh races', () => { let ethCallCount = 0 let usdcCallCount = 0 - mock.module('../../../protocol/uniswapQuoter.js', () => ({ - ETH_ADDRESS: getAddress('0x00000000000000000000000000000000000000f1'), + installRepPriceQuoterForTesting({ getRepAddress: () => repAddress, isRepPricingEnabled: () => true, quoteBestExactInputWithSource: mock(async () => { @@ -88,7 +89,7 @@ describe('useRepPrices refresh races', () => { if (usdcCallCount === 3) return await newUsdcQuote.promise throw new Error('Unexpected REP/USDC quote call') }), - })) + }) installActiveEnvironmentForTesting({ ...createFakeBackend(), createReadClient: () => createPublicClient({ transport: http('http://127.0.0.1:8545') }), @@ -151,8 +152,7 @@ describe('useRepPrices refresh races', () => { const repAddress = getAddress('0x00000000000000000000000000000000000000e2') let ethCallCount = 0 let usdcCallCount = 0 - mock.module('../../../protocol/uniswapQuoter.js', () => ({ - ETH_ADDRESS: getAddress('0x00000000000000000000000000000000000000f2'), + installRepPriceQuoterForTesting({ getRepAddress: () => repAddress, isRepPricingEnabled: () => true, quoteBestExactInputWithSource: mock(async () => { @@ -167,7 +167,7 @@ describe('useRepPrices refresh races', () => { if (usdcCallCount === 1) return { amountOut: 10n, source: { poolUrl: undefined, protocol: 'mock' as const } } throw new Error('No pool is available for the REP/USDC quote') }), - })) + }) installActiveEnvironmentForTesting({ ...createFakeBackend(), createReadClient: () => createPublicClient({ transport: http('http://127.0.0.1:8545') }), diff --git a/ui/ts/tests/features/open-oracle/useRepPrices.test.tsx b/ui/zoltar/ts/tests/features/open-oracle/useRepPrices.test.tsx similarity index 97% rename from ui/ts/tests/features/open-oracle/useRepPrices.test.tsx rename to ui/zoltar/ts/tests/features/open-oracle/useRepPrices.test.tsx index cf7927f32..cff95f820 100644 --- a/ui/ts/tests/features/open-oracle/useRepPrices.test.tsx +++ b/ui/zoltar/ts/tests/features/open-oracle/useRepPrices.test.tsx @@ -1,20 +1,20 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, waitFor, within } from '../../testUtils/queries' +import { fireEvent, waitFor, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import type { Address } from '@zoltar/shared/ethereum' import { createPublicClient, http } from '@zoltar/shared/ethereum' import { act } from 'preact/test-utils' import { render } from 'preact' -import type { SimulationController } from '../../../simulation/controller.js' +import type { SimulationController } from '@zoltar/ui-core-shared/simulation/controller.js' import { resetRepPriceCacheForTesting, useRepPrices } from '../../../features/open-oracle/hooks/useRepPrices.js' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import type { ChainBackend, ReadClient } from '../../../lib/chainBackend.js' -import { createFakeBackend, createFakeSimulationProfile } from '../../testUtils/fakeBackend.js' -import { SEPOLIA_NETWORK_PROFILE } from '../../../lib/networkProfile.js' -import { serializeSavedSimulationStateEnvelope } from '../../../simulation/savedStates.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import type { ChainBackend, ReadClient } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import { createFakeBackend, createFakeSimulationProfile } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { SEPOLIA_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { serializeSavedSimulationStateEnvelope } from '@zoltar/ui-core-shared/simulation/savedStates.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' function createSimulationController(): SimulationController { const selectedAccount = '0x00000000000000000000000000000000000000a1' as Address diff --git a/ui/ts/tests/features/reporting/escalationDepositSelectionList.test.tsx b/ui/zoltar/ts/tests/features/reporting/escalationDepositSelectionList.test.tsx similarity index 95% rename from ui/ts/tests/features/reporting/escalationDepositSelectionList.test.tsx rename to ui/zoltar/ts/tests/features/reporting/escalationDepositSelectionList.test.tsx index ebb2ed269..87ff7e2b7 100644 --- a/ui/ts/tests/features/reporting/escalationDepositSelectionList.test.tsx +++ b/ui/zoltar/ts/tests/features/reporting/escalationDepositSelectionList.test.tsx @@ -1,13 +1,13 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent } from '../../testUtils/queries' +import { fireEvent } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { act } from 'preact/test-utils' import { useState } from 'preact/hooks' import { zeroAddress } from '@zoltar/shared/ethereum' import { EscalationDepositSelectionList } from '../../../features/reporting/components/EscalationDepositSelectionList.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' describe('EscalationDepositSelectionList', () => { let restoreDomEnvironment: (() => void) | undefined diff --git a/ui/ts/tests/features/reporting/importedForkSettlementSection.test.tsx b/ui/zoltar/ts/tests/features/reporting/importedForkSettlementSection.test.tsx similarity index 95% rename from ui/ts/tests/features/reporting/importedForkSettlementSection.test.tsx rename to ui/zoltar/ts/tests/features/reporting/importedForkSettlementSection.test.tsx index bc81d349e..1706ad8de 100644 --- a/ui/ts/tests/features/reporting/importedForkSettlementSection.test.tsx +++ b/ui/zoltar/ts/tests/features/reporting/importedForkSettlementSection.test.tsx @@ -3,10 +3,10 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { useState } from 'preact/hooks' import { ImportedForkSettlementSection } from '../../../features/reporting/components/ImportedForkSettlementSection.js' -import type { ReportingOutcomeKey } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { fireEvent, within } from '../../testUtils/queries.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import type { ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' describe('ImportedForkSettlementSection', () => { let restoreDomEnvironment: (() => void) | undefined diff --git a/ui/ts/tests/features/reporting/reportingDomain.test.ts b/ui/zoltar/ts/tests/features/reporting/reportingDomain.test.ts similarity index 99% rename from ui/ts/tests/features/reporting/reportingDomain.test.ts rename to ui/zoltar/ts/tests/features/reporting/reportingDomain.test.ts index ddf980036..103045129 100644 --- a/ui/ts/tests/features/reporting/reportingDomain.test.ts +++ b/ui/zoltar/ts/tests/features/reporting/reportingDomain.test.ts @@ -23,7 +23,7 @@ import { previewReportingContribution, projectEscalationEndTime, } from '../../../features/reporting/lib/reportingDomain.js' -import type { ActiveReportingDetails, MarketDetails, ReportingDetails, ReportingOutcomeKey } from '../../../types/contracts.js' +import type { ActiveReportingDetails, MarketDetails, ReportingDetails, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' const ATTO_REP = 10n ** 18n diff --git a/ui/ts/tests/features/reporting/reportingForm.test.ts b/ui/zoltar/ts/tests/features/reporting/reportingForm.test.ts similarity index 100% rename from ui/ts/tests/features/reporting/reportingForm.test.ts rename to ui/zoltar/ts/tests/features/reporting/reportingForm.test.ts diff --git a/ui/ts/tests/features/reporting/reportingGuards.test.ts b/ui/zoltar/ts/tests/features/reporting/reportingGuards.test.ts similarity index 100% rename from ui/ts/tests/features/reporting/reportingGuards.test.ts rename to ui/zoltar/ts/tests/features/reporting/reportingGuards.test.ts diff --git a/ui/ts/tests/features/reporting/reportingSection.test.tsx b/ui/zoltar/ts/tests/features/reporting/reportingSection.test.tsx similarity index 99% rename from ui/ts/tests/features/reporting/reportingSection.test.tsx rename to ui/zoltar/ts/tests/features/reporting/reportingSection.test.tsx index 5b6e1ec78..84416ee9e 100644 --- a/ui/ts/tests/features/reporting/reportingSection.test.tsx +++ b/ui/zoltar/ts/tests/features/reporting/reportingSection.test.tsx @@ -2,20 +2,20 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { act } from 'preact/test-utils' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { h, render } from 'preact' import { useState } from 'preact/hooks' import { zeroAddress } from '@zoltar/shared/ethereum' import { ReportingSection } from '../../../features/reporting/components/ReportingSection.js' -import { formatDuration, formatTimestamp } from '../../../lib/formatters.js' +import { formatDuration, formatTimestamp } from '@zoltar/ui-core-shared/lib/formatters.js' import { getReportingLockedUntilMessage } from '../../../features/reporting/lib/reporting.js' import { computeEscalationTimeSinceStartFromAttritionCostAttoRep, ESCALATION_GAME_ACTIVATION_DELAY, getEscalationBalanceTuple, getEscalationBindingCapitalAttoRep, getSelectedOutcomeRewardWindowFillTimestamp } from '../../../features/reporting/lib/reportingDomain.js' import type { AccountState, ReportingFormState } from '../../../types/app.js' -import type { ActiveReportingDetails, EscalationDeposit, MarketDetails, ReportingDetails } from '../../../types/contracts.js' +import type { ActiveReportingDetails, EscalationDeposit, MarketDetails, ReportingDetails } from '@zoltar/ui-core-shared/types/contracts.js' import type { ReportingSectionProps } from '../../../features/types.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' const ATTO_REP = 10n ** 18n diff --git a/ui/ts/tests/features/reporting/useReportingOperations.test.tsx b/ui/zoltar/ts/tests/features/reporting/useReportingOperations.test.tsx similarity index 97% rename from ui/ts/tests/features/reporting/useReportingOperations.test.tsx rename to ui/zoltar/ts/tests/features/reporting/useReportingOperations.test.tsx index 079dba844..b1da92bd6 100644 --- a/ui/ts/tests/features/reporting/useReportingOperations.test.tsx +++ b/ui/zoltar/ts/tests/features/reporting/useReportingOperations.test.tsx @@ -1,18 +1,18 @@ /// -import { waitFor } from '../../testUtils/queries' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h } from 'preact' import { act } from 'preact/test-utils' import { getAddress, zeroAddress, type Address } from '@zoltar/shared/ethereum' -import type { ReportingDetails } from '../../../types/contracts.js' +import type { ReportingDetails } from '@zoltar/ui-core-shared/types/contracts.js' import { useReportingOperations, type UseReportingOperationsDependencies } from '../../../features/reporting/hooks/useReportingOperations.js' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { createInitialTransactionTrayState, markTransactionCanceled, markTransactionFinished, markTransactionRequested } from '../../../lib/transactionTray.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createInitialTransactionTrayState, markTransactionCanceled, markTransactionFinished, markTransactionRequested } from '@zoltar/ui-core-shared/lib/transactionTray.js' import type { TransactionIntent } from '../../../features/types.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' type UseReportingOperations = typeof useReportingOperations type UseReportingOperationsState = ReturnType diff --git a/ui/ts/tests/features/universes/childUniverseWalletAsset.test.tsx b/ui/zoltar/ts/tests/features/universes/childUniverseWalletAsset.test.tsx similarity index 80% rename from ui/ts/tests/features/universes/childUniverseWalletAsset.test.tsx rename to ui/zoltar/ts/tests/features/universes/childUniverseWalletAsset.test.tsx index fac9698fd..b2528b65e 100644 --- a/ui/ts/tests/features/universes/childUniverseWalletAsset.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/childUniverseWalletAsset.test.tsx @@ -3,12 +3,12 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { ChildUniverseDetails } from '../../../features/universes/components/ChildUniverseDetails.js' -import { installActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import { createInjectedBackend } from '../../../lib/chainBackend.js' -import type { ZoltarChildUniverseSummary } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { within } from '../../testUtils/queries.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createInjectedBackend } from '@zoltar/ui-core-shared/lib/chainBackend.js' +import type { ZoltarChildUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' const TOKEN_ADDRESS = '0x00000000000000000000000000000000000000a1' diff --git a/ui/ts/tests/features/universes/forkZoltarSection.test.tsx b/ui/zoltar/ts/tests/features/universes/forkZoltarSection.test.tsx similarity index 96% rename from ui/ts/tests/features/universes/forkZoltarSection.test.tsx rename to ui/zoltar/ts/tests/features/universes/forkZoltarSection.test.tsx index 34750a776..d19edd8c7 100644 --- a/ui/ts/tests/features/universes/forkZoltarSection.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/forkZoltarSection.test.tsx @@ -2,14 +2,14 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h, render } from 'preact' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' import { zeroAddress } from '@zoltar/shared/ethereum' import { ForkZoltarSection } from '../../../features/universes/components/ForkZoltarSection.js' -import { ChainTimestampContext } from '../../../lib/chainTimestamp.js' -import { formatRelativeTimestamp, formatTimestamp } from '../../../lib/formatters.js' -import type { MarketDetails, ZoltarUniverseSummary } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { ChainTimestampContext } from '@zoltar/ui-core-shared/lib/chainTimestamp.js' +import { formatRelativeTimestamp, formatTimestamp } from '@zoltar/ui-core-shared/lib/formatters.js' +import type { MarketDetails, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' const ATTO_REP = 10n ** 18n const ZOLTAR_ADDRESS = '0x00000000000000000000000000000000000000a1' as const diff --git a/ui/ts/tests/features/universes/migrationOutcomeUniversesSection.test.ts b/ui/zoltar/ts/tests/features/universes/migrationOutcomeUniversesSection.test.ts similarity index 95% rename from ui/ts/tests/features/universes/migrationOutcomeUniversesSection.test.ts rename to ui/zoltar/ts/tests/features/universes/migrationOutcomeUniversesSection.test.ts index 952511e65..eb40d3825 100644 --- a/ui/ts/tests/features/universes/migrationOutcomeUniversesSection.test.ts +++ b/ui/zoltar/ts/tests/features/universes/migrationOutcomeUniversesSection.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { getMigrationOutcomeHeldBalance, getMigrationOutcomeSplitLimit } from '../../../features/universes/components/MigrationOutcomeUniversesSection.js' -import type { ZoltarChildUniverseSummary } from '../../../types/contracts.js' +import type { ZoltarChildUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' void describe('getMigrationOutcomeHeldBalance', () => { void test('returns zero for undeployed child universes', () => { diff --git a/ui/ts/tests/features/universes/transactionUniverseValue.test.tsx b/ui/zoltar/ts/tests/features/universes/transactionUniverseValue.test.tsx similarity index 81% rename from ui/ts/tests/features/universes/transactionUniverseValue.test.tsx rename to ui/zoltar/ts/tests/features/universes/transactionUniverseValue.test.tsx index 46f4d92b1..d1a7d7043 100644 --- a/ui/ts/tests/features/universes/transactionUniverseValue.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/transactionUniverseValue.test.tsx @@ -2,9 +2,9 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { TransactionUniverseValue } from '../../../features/universes/components/TransactionUniverseValue.js' -import { within } from '../../testUtils/queries.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' describe('TransactionUniverseValue', () => { let restoreDomEnvironment: (() => void) | undefined diff --git a/ui/ts/tests/features/universes/universe.test.ts b/ui/zoltar/ts/tests/features/universes/universe.test.ts similarity index 87% rename from ui/ts/tests/features/universes/universe.test.ts rename to ui/zoltar/ts/tests/features/universes/universe.test.ts index 1b6917884..24dbc1d66 100644 --- a/ui/ts/tests/features/universes/universe.test.ts +++ b/ui/zoltar/ts/tests/features/universes/universe.test.ts @@ -1,10 +1,13 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { getActiveNetworkProfile, installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' +import { getActiveNetworkProfile, installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' import { getGenesisReputationTokenAddress, formatUniverseCollectionLabel, formatUniverseLabel, getUniverseLinkHref, navigateToUniverse } from '../../../features/universes/lib/universe.js' -import { createFakeBackend, createFakeSimulationProfile } from '../../testUtils/fakeBackend.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' +import { createFakeBackend, createFakeSimulationProfile } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' + +installTestRouting() void describe('universe helpers', () => { let cleanup: (() => void) | undefined diff --git a/ui/ts/tests/features/universes/universeLink.test.tsx b/ui/zoltar/ts/tests/features/universes/universeLink.test.tsx similarity index 90% rename from ui/ts/tests/features/universes/universeLink.test.tsx rename to ui/zoltar/ts/tests/features/universes/universeLink.test.tsx index 14831081c..a98922757 100644 --- a/ui/ts/tests/features/universes/universeLink.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/universeLink.test.tsx @@ -1,13 +1,15 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { fireEvent, within } from '../../testUtils/queries' +import { fireEvent, within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' import { act } from 'preact/test-utils' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' import { UniverseLink } from '../../../features/universes/components/UniverseLink.js' import { getUniverseLinkHref } from '../../../features/universes/lib/universe.js' +installTestRouting() describe('UniverseLink', () => { let restoreDomEnvironment: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/features/universes/useZoltarFork.test.tsx b/ui/zoltar/ts/tests/features/universes/useZoltarFork.test.tsx similarity index 97% rename from ui/ts/tests/features/universes/useZoltarFork.test.tsx rename to ui/zoltar/ts/tests/features/universes/useZoltarFork.test.tsx index 08c3ac8c4..727d43460 100644 --- a/ui/ts/tests/features/universes/useZoltarFork.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/useZoltarFork.test.tsx @@ -4,12 +4,12 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h, render } from 'preact' import { act } from 'preact/test-utils' import { type Address, getAddress, type Hash, zeroAddress } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' import { useZoltarFork, type UseZoltarForkDependencies } from '../../../features/universes/hooks/useZoltarFork.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import type { MarketDetails, ZoltarUniverseSummary } from '../../../types/contracts.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import type { MarketDetails, ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' type UseZoltarForkState = ReturnType diff --git a/ui/ts/tests/features/universes/useZoltarMigration.test.tsx b/ui/zoltar/ts/tests/features/universes/useZoltarMigration.test.tsx similarity index 94% rename from ui/ts/tests/features/universes/useZoltarMigration.test.tsx rename to ui/zoltar/ts/tests/features/universes/useZoltarMigration.test.tsx index 35a1448fd..efc9d67f6 100644 --- a/ui/ts/tests/features/universes/useZoltarMigration.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/useZoltarMigration.test.tsx @@ -4,11 +4,11 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h } from 'preact' import { act } from 'preact/test-utils' import { getAddress, type Hash, zeroAddress } from '@zoltar/shared/ethereum' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' -import type { ZoltarUniverseSummary } from '../../../types/contracts.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' type UseZoltarMigration = typeof import('../../../features/universes/hooks/useZoltarMigration.js')['useZoltarMigration'] type UseZoltarMigrationState = ReturnType @@ -78,7 +78,7 @@ describe('useZoltarMigration', () => { transactionFailures.push(message) } - mock.module('../../../lib/clients.js', () => ({ + mock.module('@zoltar/ui-core-shared/lib/clients.js', () => ({ createWalletWriteClient: mock(() => ({ kind: 'write-client', })), @@ -199,7 +199,7 @@ describe('useZoltarMigration', () => { throw new Error('prepareRepForMigrationInZoltar should not be called in this test') }), })) - mock.module('../../../lib/clients.js', () => ({ + mock.module('@zoltar/ui-core-shared/lib/clients.js', () => ({ createWalletWriteClient: mock(() => ({ kind: 'write-client' })), })) diff --git a/ui/ts/tests/features/universes/useZoltarUniverse.test.tsx b/ui/zoltar/ts/tests/features/universes/useZoltarUniverse.test.tsx similarity index 97% rename from ui/ts/tests/features/universes/useZoltarUniverse.test.tsx rename to ui/zoltar/ts/tests/features/universes/useZoltarUniverse.test.tsx index a7ae42cb4..8bfadef83 100644 --- a/ui/ts/tests/features/universes/useZoltarUniverse.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/useZoltarUniverse.test.tsx @@ -4,13 +4,13 @@ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { h, render } from 'preact' import { act } from 'preact/test-utils' import { createPublicClient, getAddress, http, zeroAddress, type Hash } from '@zoltar/shared/ethereum' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../../lib/activeEnvironment.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' import { useZoltarUniverse, type UseZoltarUniverseDependencies } from '../../../features/universes/hooks/useZoltarUniverse.js' -import type { DeploymentStatus, MarketDetails } from '../../../types/contracts.js' -import { createFakeBackend } from '../../testUtils/fakeBackend.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { waitFor } from '../../testUtils/queries.js' +import type { DeploymentStatus, MarketDetails } from '@zoltar/ui-core-shared/types/contracts.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { waitFor } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' type UseZoltarUniverseState = ReturnType diff --git a/ui/ts/tests/features/universes/zoltarMigrationGuards.test.ts b/ui/zoltar/ts/tests/features/universes/zoltarMigrationGuards.test.ts similarity index 95% rename from ui/ts/tests/features/universes/zoltarMigrationGuards.test.ts rename to ui/zoltar/ts/tests/features/universes/zoltarMigrationGuards.test.ts index aed91d6cd..45f3478ee 100644 --- a/ui/ts/tests/features/universes/zoltarMigrationGuards.test.ts +++ b/ui/zoltar/ts/tests/features/universes/zoltarMigrationGuards.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from 'bun:test' import { zeroAddress } from '@zoltar/shared/ethereum' import { getMigrationGuardMessage } from '../../../features/universes/lib/zoltarMigrationGuards.js' -import type { ZoltarUniverseSummary } from '../../../types/contracts.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' function createUniverse(overrides: Partial = {}): ZoltarUniverseSummary { return { diff --git a/ui/ts/tests/features/universes/zoltarMigrationSection.test.tsx b/ui/zoltar/ts/tests/features/universes/zoltarMigrationSection.test.tsx similarity index 94% rename from ui/ts/tests/features/universes/zoltarMigrationSection.test.tsx rename to ui/zoltar/ts/tests/features/universes/zoltarMigrationSection.test.tsx index 62767767a..ff03edae2 100644 --- a/ui/ts/tests/features/universes/zoltarMigrationSection.test.tsx +++ b/ui/zoltar/ts/tests/features/universes/zoltarMigrationSection.test.tsx @@ -1,15 +1,16 @@ /// import { afterEach, beforeEach, describe, expect, test } from 'bun:test' -import { within } from '../../testUtils/queries' +import { within } from '@zoltar/ui-core-shared/tests/testUtils/queries.js' +import { installTestRouting } from '@zoltar/ui-core-shared/tests/testUtils/testRouting.js' import { h } from 'preact' import { zeroAddress } from '@zoltar/shared/ethereum' import { ZoltarMigrationSection } from '../../../features/universes/components/ZoltarMigrationSection.js' import type { ZoltarMigrationFormState } from '../../../types/app.js' -import type { ZoltarUniverseSummary } from '../../../types/contracts.js' -import { installDomEnvironment } from '../../testUtils/domEnvironment.js' -import { renderIntoDocument } from '../../testUtils/renderIntoDocument.js' -import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '../../testUtils/transactionActionButton.js' +import type { ZoltarUniverseSummary } from '@zoltar/ui-core-shared/types/contracts.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { renderIntoDocument } from '@zoltar/ui-core-shared/tests/testUtils/renderIntoDocument.js' +import { expectTransactionButtonDisabled, expectTransactionButtonEnabled } from '@zoltar/ui-core-shared/tests/testUtils/transactionActionButton.js' type ZoltarMigrationSectionProps = Parameters[0] const ATTO_REP = 10n ** 18n @@ -80,6 +81,7 @@ function createProps(overrides: Partial = {}): Zolt } } +installTestRouting() describe('ZoltarMigrationSection', () => { let restoreDomEnvironment: (() => void) | undefined let cleanupRenderedComponent: (() => Promise) | undefined diff --git a/ui/ts/tests/simulation/activeEnvironment.test.ts b/ui/zoltar/ts/tests/integration/activeEnvironment.test.ts similarity index 97% rename from ui/ts/tests/simulation/activeEnvironment.test.ts rename to ui/zoltar/ts/tests/integration/activeEnvironment.test.ts index 398589364..4e38112a0 100644 --- a/ui/ts/tests/simulation/activeEnvironment.test.ts +++ b/ui/zoltar/ts/tests/integration/activeEnvironment.test.ts @@ -3,15 +3,15 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test' import { getAddress } from '@zoltar/shared/ethereum' import { loadDeploymentStatusOracleSnapshot, loadErc20Balance } from '../../protocol/index.js' -import { getChainDisplayLabel, getChainIdDecimalLabel, getWalletScopedAccountAddress, getWrongNetworkMessage, isActiveAppChain, isSupportedAppChain } from '../../lib/network.js' -import { getActiveBackend, initializeActiveEnvironment, installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting, shouldUseSimulationLocation } from '../../lib/activeEnvironment.js' -import { SIMULATION_BLOCK_INTERVAL_SECONDS, SIMULATION_INITIAL_TIMESTAMP } from '../../simulation/clock.js' -import { parseSavedSimulationStateEnvelope, persistSavedSimulationState, serializeSavedSimulationStateEnvelope } from '../../simulation/savedStates.js' -import { createSimulationBackend } from '../../simulation/tevmBackend.js' -import { createFakeBackend, createFakeSimulationProfile } from '../testUtils/fakeBackend.js' -import { MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE, type NetworkProfile } from '../../lib/networkProfile.js' -import { installDomEnvironment } from '../testUtils/domEnvironment.js' -import { createBootstrappedSimulationBackendWithRetry, resetSelectedAccountAndTransactionDelay, type SimulationBackend } from './testUtils.js' +import { getChainDisplayLabel, getChainIdDecimalLabel, getWalletScopedAccountAddress, getWrongNetworkMessage, isActiveAppChain, isSupportedAppChain } from '@zoltar/ui-core-shared/lib/network.js' +import { getActiveBackend, initializeActiveEnvironment, installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting, shouldUseSimulationLocation } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { SIMULATION_BLOCK_INTERVAL_SECONDS, SIMULATION_INITIAL_TIMESTAMP } from '@zoltar/ui-core-shared/simulation/clock.js' +import { parseSavedSimulationStateEnvelope, persistSavedSimulationState, serializeSavedSimulationStateEnvelope } from '@zoltar/ui-core-shared/simulation/savedStates.js' +import { createSimulationBackend } from '@zoltar/ui-core-shared/simulation/tevmBackend.js' +import { createFakeBackend, createFakeSimulationProfile } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE, type NetworkProfile } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { installDomEnvironment } from '@zoltar/ui-core-shared/tests/testUtils/domEnvironment.js' +import { createBootstrappedSimulationBackendWithRetry, resetSelectedAccountAndTransactionDelay, type SimulationBackend } from '@zoltar/ui-core-shared/tests/simulationTestUtils.js' const DEFAULT_SIMULATION_REP_PER_ETH_PRICE = 3n * 10n ** 18n const SIMULATION_REP_MINT_AMOUNT = 1_000_000n * 10n ** 18n @@ -523,7 +523,7 @@ void describe('simulation backend', () => { expect(wethCode).not.toBe('0x') expect(repBalanceAttoRep > 0n).toBe(true) expect(wethBalanceAttoEth > 0n).toBe(true) - expect(deploymentSnapshot.augurStatoblastDeployed).toBe(false) + expect(deploymentSnapshot.applicationDeploymentComplete).toBe(false) expect(deploymentSnapshot.deploymentStatuses.every(step => step.deployed === false)).toBe(true) }, 30_000) diff --git a/ui/ts/tests/simulation/deployedEnvironment.test.ts b/ui/zoltar/ts/tests/integration/deployedEnvironment.test.ts similarity index 85% rename from ui/ts/tests/simulation/deployedEnvironment.test.ts rename to ui/zoltar/ts/tests/integration/deployedEnvironment.test.ts index 1aef80003..bd486e20c 100644 --- a/ui/ts/tests/simulation/deployedEnvironment.test.ts +++ b/ui/zoltar/ts/tests/integration/deployedEnvironment.test.ts @@ -3,7 +3,8 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test' import { loadDeploymentStatusOracleSnapshot, loadZoltarUniverseSummary } from '../../protocol/index.js' import { getDeploymentSteps } from '../../protocol/deployment.js' -import { createBootstrappedSimulationBackendWithRetry, resetSelectedAccountAndTransactionDelay, type SimulationBackend } from './testUtils.js' +import { resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createBootstrappedSimulationBackendWithRetry, resetSelectedAccountAndTransactionDelay, type SimulationBackend } from '@zoltar/ui-core-shared/tests/simulationTestUtils.js' const SIMULATION_REP_MINT_AMOUNT = 1_000_000n * 10n ** 18n @@ -21,6 +22,7 @@ void describe('deployed simulation backend', () => { afterAll(async () => { if (deployedBackend !== undefined) await deployedBackend.dispose() + resetActiveEnvironmentForTesting() }, 30_000) void test('keeps the deployed-scenario fork threshold in sync after minting REP', async () => { @@ -51,7 +53,8 @@ void describe('deployed simulation backend', () => { const deploymentSnapshot = await loadDeploymentStatusOracleSnapshot(deployedBackend.createReadClient()) expect(deployedBackend.currentScenario).toBe('deployed') - expect(deploymentSnapshot.augurStatoblastDeployed).toBe(true) + expect(deploymentSnapshot.applicationDeploymentComplete).toBe(true) expect(deploymentSnapshot.deploymentStatuses.every(step => step.deployed)).toBe(true) + expect(deploymentSnapshot.deploymentStatuses.some(step => step.id === 'securityPoolFactory')).toBe(false) }, 30_000) }) diff --git a/ui/ts/tests/protocol/core.test.ts b/ui/zoltar/ts/tests/protocol/core.test.ts similarity index 97% rename from ui/ts/tests/protocol/core.test.ts rename to ui/zoltar/ts/tests/protocol/core.test.ts index a8560a4e5..3f15b3b1c 100644 --- a/ui/ts/tests/protocol/core.test.ts +++ b/ui/zoltar/ts/tests/protocol/core.test.ts @@ -2,12 +2,12 @@ import { describe, expect, mock, test } from 'bun:test' import { encodeFunctionData, getAddress, type Hash, type TransactionReceipt } from '@zoltar/shared/ethereum' -import { ABIS } from '../../abis.js' +import { ABIS } from '@zoltar/ui-core-shared/abis.js' import { getMulticall3Address } from '../../protocol/deploymentHelpers.js' import { readOptionalMulticall, readRequiredMulticall, writeContractAndWait, writeContractAndWaitForReceipt } from '../../protocol/core.js' -import { buildIntent, createPreparedWalletPresentation } from '../../lib/transactionPresentations.js' -import type { GlobalTransactionPresentation } from '../../types/components.js' -import type { ReadClient, WriteClient } from '../../types/contracts.js' +import { buildIntent, createPreparedWalletPresentation } from '@zoltar/ui-core-shared/lib/transactionPresentations.js' +import type { GlobalTransactionPresentation } from '@zoltar/ui-core-shared/types/components.js' +import type { ReadClient, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' type MulticallRequest = Parameters[0] type WriteContractClient = Pick & diff --git a/ui/ts/tests/protocol/deployment.test.ts b/ui/zoltar/ts/tests/protocol/deployment.test.ts similarity index 98% rename from ui/ts/tests/protocol/deployment.test.ts rename to ui/zoltar/ts/tests/protocol/deployment.test.ts index e6ec55838..5e7c722b0 100644 --- a/ui/ts/tests/protocol/deployment.test.ts +++ b/ui/zoltar/ts/tests/protocol/deployment.test.ts @@ -7,17 +7,17 @@ import { type Address, type Hash, type Hex, type TransactionReceipt, encodeDeplo import { getDeploymentSteps, loadDeploymentStatusOracleSnapshot, loadErc20Allowance, loadErc20Balance } from '../../protocol/index.js' import { getGenesisReputationTokenAddress } from '../../protocol/activeProtocolAddresses.js' import { PROXY_DEPLOYER_ADDRESS, ZERO_SALT } from '../../protocol/deploymentHelpers.js' -import type { ReadClient, WriteClient } from '../../types/contracts.js' -import { installActiveEnvironmentForTesting } from '../../lib/activeEnvironment.js' -import { createInitialTransactionTrayState, markTransactionPrepared, markTransactionRequested } from '../../lib/transactionTray.js' -import { createFakeBackend, createFakeSimulationProfile } from '../testUtils/fakeBackend.js' -import { MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE } from '../../lib/networkProfile.js' -import { SEPOLIA_GENESIS_REP_INIT_CODE, SEPOLIA_WETH_INIT_CODE } from '../../lib/sepoliaDeploymentConfig.js' -import { DeploymentStatusOracle_DeploymentStatusOracle, ScalarOutcomes_ScalarOutcomes, statoblast_factories_UniformPriceDualCapBatchAuctionFactory_UniformPriceDualCapBatchAuctionFactory } from '../../contractArtifact.js' +import type { ReadClient, WriteClient } from '@zoltar/ui-core-shared/types/contracts.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { createInitialTransactionTrayState, markTransactionPrepared, markTransactionRequested } from '@zoltar/ui-core-shared/lib/transactionTray.js' +import { createFakeBackend, createFakeSimulationProfile } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' +import { MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { SEPOLIA_GENESIS_REP_INIT_CODE, SEPOLIA_WETH_INIT_CODE } from '@zoltar/ui-core-shared/lib/sepoliaDeploymentConfig.js' +import { DeploymentStatusOracle_DeploymentStatusOracle, ScalarOutcomes_ScalarOutcomes, statoblast_factories_UniformPriceDualCapBatchAuctionFactory_UniformPriceDualCapBatchAuctionFactory } from '@zoltar/ui-core-shared/contractArtifact.js' import { ATOMIC_FUNDING_BYTECODE, ATOMIC_FUNDING_SOURCE, EXPECTED_SEPOLIA_DEPLOYMENT_RUNTIME_CODE_HASHES, PROXY_DEPLOYER_RUNTIME_CODE, STATIC_DEPLOYMENT_ARTIFACT_RUNTIME_CODE_BY_STEP_ID, assertStaticDeploymentArtifactRuntimeCodeHashes } from '../../protocol/deployment.js' const require = createRequire(import.meta.url) -const rootSolcPath = fileURLToPath(new URL('../../../../node_modules/solc/index.js', import.meta.url)) +const rootSolcPath = fileURLToPath(new URL('../../../../../node_modules/solc/index.js', import.meta.url)) const solc: { compile: (input: string) => string; version: () => string } = require(rootSolcPath) function isRecord(value: unknown): value is Record { @@ -69,7 +69,7 @@ function createMockReadClient({ getCode, readContract }: { getCode: MockReadClie describe('contract deployment internals', () => { test('rejects generated deployment artifacts that do not match the pinned runtime hashes', () => { - expect(Object.keys(STATIC_DEPLOYMENT_ARTIFACT_RUNTIME_CODE_BY_STEP_ID).sort()).toEqual(['deploymentStatusOracle', 'escalationGameClaimDelegate', 'multicall3', 'openOracle', 'scalarOutcomes', 'uniformPriceDualCapBatchAuctionFactory', 'weth', 'zoltarQuestionData']) + expect(Object.keys(STATIC_DEPLOYMENT_ARTIFACT_RUNTIME_CODE_BY_STEP_ID).sort()).toEqual(['deploymentStatusOracle', 'multicall3', 'openOracle', 'scalarOutcomes', 'uniformPriceDualCapBatchAuctionFactory', 'weth', 'zoltarQuestionData']) expect(() => assertStaticDeploymentArtifactRuntimeCodeHashes()).not.toThrow() expect(() => assertStaticDeploymentArtifactRuntimeCodeHashes({ @@ -288,7 +288,7 @@ describe('contract deployment internals', () => { const snapshot = await loadDeploymentStatusOracleSnapshot(readClient as ReadClient) expect(readContractCalls).toEqual(['getDeploymentMask']) - expect(snapshot.augurStatoblastDeployed).toBe(false) + expect(snapshot.applicationDeploymentComplete).toBe(false) expect(snapshot.deploymentStatuses.find(step => step.id === 'proxyDeployer')?.deployed).toBe(true) expect(snapshot.deploymentStatuses.find(step => step.id === 'deploymentStatusOracle')?.deployed).toBe(true) expect(snapshot.deploymentStatuses.find(step => step.id === 'multicall3')?.deployed).toBe(false) @@ -317,8 +317,8 @@ describe('contract deployment internals', () => { const steps = createDeploymentSteps() const oracleStep = steps.find(step => step.id === 'deploymentStatusOracle') if (oracleStep === undefined) throw new Error('Expected deploymentStatusOracle step') - const factoryStep = steps.find(step => step.id === 'securityPoolFactory') - if (factoryStep === undefined) throw new Error('Expected securityPoolFactory step') + const factoryStep = steps.find(step => step.id === 'openOracle') + if (factoryStep === undefined) throw new Error('Expected openOracle step') let capturedProxyDeployData: `0x${string}` | undefined let capturedFactoryData: `0x${string}` | undefined diff --git a/ui/ts/tests/protocol/helpers.test.ts b/ui/zoltar/ts/tests/protocol/helpers.test.ts similarity index 100% rename from ui/ts/tests/protocol/helpers.test.ts rename to ui/zoltar/ts/tests/protocol/helpers.test.ts diff --git a/ui/ts/tests/protocol/openOracle.test.ts b/ui/zoltar/ts/tests/protocol/openOracle.test.ts similarity index 98% rename from ui/ts/tests/protocol/openOracle.test.ts rename to ui/zoltar/ts/tests/protocol/openOracle.test.ts index 2317a5c5e..1e3678dab 100644 --- a/ui/ts/tests/protocol/openOracle.test.ts +++ b/ui/zoltar/ts/tests/protocol/openOracle.test.ts @@ -20,9 +20,9 @@ import { withdrawOpenOracleBalance, type LiquidationApprovalParams, } from '../../protocol/index.js' -import { statoblast_LiquidationApprovalRegistry_LiquidationApprovalRegistry, statoblast_openOracle_OpenOracle_OpenOracle } from '../../contractArtifact.js' -import { MAINNET_WETH_ADDRESS } from '../../lib/networkProfile.js' -import { asWriteClient, createBlockWithTimestamp, createMockLoaderClient, createMockWriteClient, getContractFunctionName } from './testSupport.js' +import { statoblast_LiquidationApprovalRegistry_LiquidationApprovalRegistry, statoblast_openOracle_OpenOracle_OpenOracle } from '@zoltar/ui-core-shared/contractArtifact.js' +import { MAINNET_WETH_ADDRESS } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { asWriteClient, createBlockWithTimestamp, createMockLoaderClient, createMockWriteClient, getContractFunctionName } from '@zoltar/ui-core-shared/tests/testUtils/protocolTestSupport.js' const vaultAddress = getAddress('0x00000000000000000000000000000000000000c1') const alternateSecurityPoolAddress = getAddress('0x00000000000000000000000000000000000000a2') diff --git a/ui/ts/tests/protocol/openOracleFunding.test.ts b/ui/zoltar/ts/tests/protocol/openOracleFunding.test.ts similarity index 100% rename from ui/ts/tests/protocol/openOracleFunding.test.ts rename to ui/zoltar/ts/tests/protocol/openOracleFunding.test.ts diff --git a/ui/ts/tests/protocol/oracleQueueOperation.test.ts b/ui/zoltar/ts/tests/protocol/oracleQueueOperation.test.ts similarity index 100% rename from ui/ts/tests/protocol/oracleQueueOperation.test.ts rename to ui/zoltar/ts/tests/protocol/oracleQueueOperation.test.ts diff --git a/ui/ts/tests/protocol/reporting.test.ts b/ui/zoltar/ts/tests/protocol/reporting.test.ts similarity index 99% rename from ui/ts/tests/protocol/reporting.test.ts rename to ui/zoltar/ts/tests/protocol/reporting.test.ts index d1933d73d..05e70d7ee 100644 --- a/ui/ts/tests/protocol/reporting.test.ts +++ b/ui/zoltar/ts/tests/protocol/reporting.test.ts @@ -3,9 +3,9 @@ import { describe, expect, test } from 'bun:test' import { concatHex, decodeFunctionData, encodeAbiParameters, getAddress, keccak256, parseAbiParameters, zeroAddress, type Address, type Hex } from '@zoltar/shared/ethereum' import { buildForkCarriedEscalationProofs, loadEscalationDeposits, loadReportingDetails, claimParentEscalationDeposits, migrateVaultWithUnresolvedEscalation, withdrawForkedEscalationDeposits } from '../../protocol/index.js' -import { statoblast_SecurityPool_SecurityPool, statoblast_SecurityPoolForker_SecurityPoolForker } from '../../contractArtifact.js' -import type { EscalationSide } from '../../types/contracts.js' -import { asWriteClient, createBlockWithTimestamp, createMockReadClient, createMockWriteClient, createMulticallStub, createReadContractStub, getContractFunctionName, mockTransactionHash, type MockReadContractHandler } from './testSupport.js' +import { statoblast_SecurityPool_SecurityPool, statoblast_SecurityPoolForker_SecurityPoolForker } from '@zoltar/ui-core-shared/contractArtifact.js' +import type { EscalationSide } from '@zoltar/ui-core-shared/types/contracts.js' +import { asWriteClient, createBlockWithTimestamp, createMockReadClient, createMockWriteClient, createMulticallStub, createReadContractStub, getContractFunctionName, mockTransactionHash, type MockReadContractHandler } from '@zoltar/ui-core-shared/tests/testUtils/protocolTestSupport.js' const securityPoolAddress = getAddress('0x00000000000000000000000000000000000000a1') const vaultAddress = getAddress('0x00000000000000000000000000000000000000c1') diff --git a/ui/ts/tests/protocol/uniswapQuoter.fork.test.ts b/ui/zoltar/ts/tests/protocol/uniswapQuoter.fork.test.ts similarity index 98% rename from ui/ts/tests/protocol/uniswapQuoter.fork.test.ts rename to ui/zoltar/ts/tests/protocol/uniswapQuoter.fork.test.ts index 6f3884f2a..f6d803de2 100644 --- a/ui/ts/tests/protocol/uniswapQuoter.fork.test.ts +++ b/ui/zoltar/ts/tests/protocol/uniswapQuoter.fork.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, describe as baseDescribe, expect, test } from 'bun:test' import { createPublicClient, http, mainnet } from '@zoltar/shared/ethereum' -import { resolveAnvilBinary } from '../../../../solidity/ts/testSupport/simulator/anvilNode' +import { resolveAnvilBinary } from '../../../../../solidity/ts/testSupport/simulator/anvilNode' import { ETH_ADDRESS, REP_ADDRESS, USDC_ADDRESS, quoteExactInput, quoteRepForEthV3 } from '../../protocol/uniswapQuoter.js' const PINNED_MAINNET_BLOCK = 22_000_000n diff --git a/ui/ts/tests/protocol/uniswapQuoter.integration.test.ts b/ui/zoltar/ts/tests/protocol/uniswapQuoter.integration.test.ts similarity index 100% rename from ui/ts/tests/protocol/uniswapQuoter.integration.test.ts rename to ui/zoltar/ts/tests/protocol/uniswapQuoter.integration.test.ts diff --git a/ui/ts/tests/protocol/uniswapQuoter.test.ts b/ui/zoltar/ts/tests/protocol/uniswapQuoter.test.ts similarity index 98% rename from ui/ts/tests/protocol/uniswapQuoter.test.ts rename to ui/zoltar/ts/tests/protocol/uniswapQuoter.test.ts index d2cfc6f3a..57f6940a8 100644 --- a/ui/ts/tests/protocol/uniswapQuoter.test.ts +++ b/ui/zoltar/ts/tests/protocol/uniswapQuoter.test.ts @@ -21,10 +21,10 @@ import { quoteRepForUsdcV4WithSource, quoteTokenForEth, } from '../../protocol/uniswapQuoter.js' -import type { ReadClient } from '../../lib/clients.js' -import { installActiveEnvironmentForTesting } from '../../lib/activeEnvironment.js' -import { MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE } from '../../lib/networkProfile.js' -import { createFakeBackend } from '../testUtils/fakeBackend.js' +import type { ReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { installActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { MAINNET_NETWORK_PROFILE, SEPOLIA_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { createFakeBackend } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' type SimulateArgs = Parameters[0] type RawSimulateParam = { poolKey: { diff --git a/ui/ts/tests/simulation/uniswapQuoter.test.ts b/ui/zoltar/ts/tests/protocol/uniswapQuoterSimulation.test.ts similarity index 90% rename from ui/ts/tests/simulation/uniswapQuoter.test.ts rename to ui/zoltar/ts/tests/protocol/uniswapQuoterSimulation.test.ts index c339f1770..94df90a04 100644 --- a/ui/ts/tests/simulation/uniswapQuoter.test.ts +++ b/ui/zoltar/ts/tests/protocol/uniswapQuoterSimulation.test.ts @@ -3,11 +3,11 @@ import { afterEach, describe, expect, test } from 'bun:test' import { createPublicClient, getAddress, http } from '@zoltar/shared/ethereum' import { USDC_ADDRESS, quoteEthForToken, quoteExactInput, quoteRepForEth, quoteTokenForEth } from '../../protocol/uniswapQuoter.js' -import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '../../lib/activeEnvironment.js' -import { serializeSavedSimulationStateEnvelope } from '../../simulation/savedStates.js' -import type { ReadClient } from '../../lib/clients.js' -import { MAINNET_NETWORK_PROFILE } from '../../lib/networkProfile.js' -import { createFakeBackend, createFakeSimulationProfile } from '../testUtils/fakeBackend.js' +import { installActiveEnvironmentForTesting, resetActiveEnvironmentForTesting } from '@zoltar/ui-core-shared/lib/activeEnvironment.js' +import { serializeSavedSimulationStateEnvelope } from '@zoltar/ui-core-shared/simulation/savedStates.js' +import type { ReadClient } from '@zoltar/ui-core-shared/lib/clients.js' +import { MAINNET_NETWORK_PROFILE } from '@zoltar/ui-core-shared/lib/networkProfile.js' +import { createFakeBackend, createFakeSimulationProfile } from '@zoltar/ui-core-shared/tests/testUtils/fakeBackend.js' afterEach(() => { resetActiveEnvironmentForTesting() diff --git a/ui/ts/tests/protocol/zoltar.test.ts b/ui/zoltar/ts/tests/protocol/zoltar.test.ts similarity index 100% rename from ui/ts/tests/protocol/zoltar.test.ts rename to ui/zoltar/ts/tests/protocol/zoltar.test.ts diff --git a/ui/zoltar/ts/types/app.ts b/ui/zoltar/ts/types/app.ts new file mode 100644 index 000000000..ebffc7d6c --- /dev/null +++ b/ui/zoltar/ts/types/app.ts @@ -0,0 +1,61 @@ +import type { MarketType, ReportingOutcomeKey } from '@zoltar/ui-core-shared/types/contracts.js' + +export type { AccountState, WriteOperationsParameters } from '@zoltar/ui-core-shared/types/app.js' + +export type Route = 'deploy' | 'zoltar' | 'open-oracle' | 'not-found' + +export type MarketFormState = { + answerUnit: string + categoricalOutcomes: string[] + description: string + scalarIncrement: string + scalarMax: string + scalarMin: string + title: string + endTime: string + marketType: MarketType + startTime: string +} + +export type OpenOracleFormState = { + amount1: string + amount2: string + disputeNewAmount1: string + disputeNewAmount2: string + disputeTokenToSwap: 'token1' | 'token2' + reportId: string + stateHash: string +} + +export type OpenOracleCreateFormState = { + ethValue: string + exactToken1Report: string + initialToken2Amount: string + escalationHalt: string + feePercentage: string + multiplier: string + protocolFee: string + settlementTime: string + settlerRewardEthAmount: string + token1Address: string + token2Address: string + disputeDelay: string +} + +export type ReportingWithdrawDepositIndexesByOutcome = { + invalid: bigint[] + yes: bigint[] + no: bigint[] +} + +export type ReportingFormState = { + reportAmount: string + securityPoolAddress: string + selectedOutcome: ReportingOutcomeKey | undefined + selectedWithdrawDepositIndexesByOutcome: ReportingWithdrawDepositIndexesByOutcome +} + +export type ZoltarMigrationFormState = { + amount: string + outcomeIndexes: string +} diff --git a/ui/zoltar/tsconfig.json b/ui/zoltar/tsconfig.json new file mode 100644 index 000000000..fe98a5442 --- /dev/null +++ b/ui/zoltar/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@zoltar/shared/*": [ + "../../shared/js/*" + ], + "@zoltar/ui-core-shared/*": [ + "../coreShared/js/*" + ] + }, + "rootDir": "./ts", + "outDir": "./js" + }, + "include": [ + "./ts/**/*.ts", + "./ts/**/*.tsx" + ], + "exclude": [ + "./ts/tests/**/*.ts", + "./ts/tests/**/*.tsx" + ] +}