diff --git a/.gitignore b/.gitignore index f927ba5..82c1261 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -.superpowers/ .worktrees/ + +# ephemeral brainstorm-server session state +.superpowers/brainstorm/.last-port +.superpowers/brainstorm/.last-token +.superpowers/brainstorm/*/state/ diff --git a/.superpowers/brainstorm/10582-1786998923/content/architecture-terminal-orchestrated.html b/.superpowers/brainstorm/10582-1786998923/content/architecture-terminal-orchestrated.html new file mode 100644 index 0000000..7fb13f6 --- /dev/null +++ b/.superpowers/brainstorm/10582-1786998923/content/architecture-terminal-orchestrated.html @@ -0,0 +1,97 @@ +
Design review · Section 1 of 4
+

Terminal-orchestrated architecture

+

All state-changing demo actions originate in auditable Foundry scripts. The operations console is a read-only window into the chain.

+ +
+
System boundaries and data flow
+
+
+
+
+
Codex + presenter
+ Foundry terminal +
+ DeployV1.s.sol
+ SeedDemo.s.sol
+ UpgradeV2.s.sol
+ forge test / inspect +
+
Only state-changing control plane
+
+ +
+
Audience view
+ React operations console +
+ Balances + total liabilities
+ USDC reserves + reserve ratio
+ Proxy / implementation / version
+ Event-driven activity timeline +
+
Read-only via viem
+
+
+ +
+
+
+
+
+ +
+
Same interface, selectable target
+ Ethereum JSON-RPC +
+
+ Anvil
Default live demo
fast + deterministic
+
+
+ Base Sepolia
Optional public proof
same scripts
+
+
+
Network config supplies RPC URL, chain ID, and deployed addresses.
+
+ +
+ +
+
+
Stable public address
+ ERC1967 Proxy +
Owns all bank storage: owner, asset, paused flag, balances, total liabilities.
+
+
delegatecall ↓
+
+
+ Bank V1
deposit
withdraw
pause
+
+
+ Bank V2
V1 behavior
+ internal transfer
+
+
+
+ MockUSDC
The proxy holds reserves; liabilities must never exceed its token balance. +
+
+
+ +
+ V1 prepared: deploy, seed accounts, deposit, withdraw + + Codex live: implement + test V2 + + Reveal: upgrade, transfer, prove preserved state +
+
+
+ +
+
+
+

Architecture approved

Continue to contract responsibilities and invariants.

+
+
+
+

Needs changes

Select this and describe the adjustment in the terminal.

+
+
diff --git a/.superpowers/brainstorm/10582-1786998923/content/dashboard-layout.html b/.superpowers/brainstorm/10582-1786998923/content/dashboard-layout.html new file mode 100644 index 0000000..666b3b5 --- /dev/null +++ b/.superpowers/brainstorm/10582-1786998923/content/dashboard-layout.html @@ -0,0 +1,98 @@ +

What should the demo dashboard emphasize?

+

All three support connect wallet, mock-USDC deposits and withdrawals, and the V1 → V2 upgrade. Click the layout that tells the clearest story.

+ +
+
+
+
+ MY BANK0x71…9a2 +
+
+
AVAILABLE BALANCE
+
1,250.00 USDC
+
Backed 1:1 by contract reserves
+
+
+ + +
+
+
Recent activityView all
+
Deposit+500 USDC
+
Withdrawal−50 USDC
+
+
Bank V1 · Proxy 0x8B…42
+
+
+

A · Customer-first

+

Feels like a consumer banking app. The upgrade story stays mostly behind the scenes.

+
+
+ +
+
+
+ BANK OPS +
Overview
+
Accounts
+
Reserves
+
Upgrades
+
+
+
System overview● Healthy
+
+
Total deposits
25,000 USDC
+
Reserve ratio
100.0%
+
Proxy version
V1
+
Status
Active
+
+
+ Contract addresses
+ Proxy       0x8B31…A942
+ Implementation 0x19F4…71C0
+ Asset       Mock USDC +
+ +
+
+
+

B · Operations console

+

Highlights reserves, proxy state, and owner controls. Strong technically, less relatable to customers.

+
+
+ +
+
+
+ UUPS BANK LAB● V1 live +
+
+
+
+ ALICE'S BALANCE
1,250 USDC
+
+
+ +
+
+ Transfer to another customer
Unlocks after V2 upgrade +
+
+
+ Upgrade journey +
1 · V1 deployed
Deposit + withdraw
+
2 · Upgrade V2
Owner only
+
3 · Transfer
Balances persist
+ +
+
+
+ Proxy stays 0x8B…42Reserves 100% +
+
+
+

C · Upgrade demo studio — recommended

+

Pairs a usable customer account with a guided UUPS timeline, making state preservation obvious.

+
+
+
diff --git a/.superpowers/brainstorm/10582-1786998923/content/demo-sequence.html b/.superpowers/brainstorm/10582-1786998923/content/demo-sequence.html new file mode 100644 index 0000000..87904ad --- /dev/null +++ b/.superpowers/brainstorm/10582-1786998923/content/demo-sequence.html @@ -0,0 +1,94 @@ +
Design review · Section 3 of 4
+

The live demo as a three-act story

+

The audience sees every command, every test, and every state transition. The operations console mirrors the chain without controlling it.

+ +
+
+
Act 1 · Prepared V1
+

Establish trust

+
make demo-v1
+
    +
  1. Deploy MockUSDC, BankV1, and proxy
  2. +
  3. Mint demo funds to Alice and Bob
  4. +
  5. Deposit 1,000 + 500 USDC
  6. +
  7. Alice withdraws 100 USDC
  8. +
+
+ Console proves
+ Proxy: 0x8B…42
+ Version: V1
+ Reserves: 1,400
+ Liabilities: 1,400 +
+
+ +
+ +
+
Act 2 · Codex live
+

Change the system safely

+
+ Ask Codex to add customer-to-customer transfers while preserving all V1 state. +
+
    +
  1. Implement BankV2
  2. +
  3. Add unit, fuzz, and invariant tests
  4. +
  5. Add upgrade and transfer scripts
  6. +
  7. Update ABI and console timeline
  8. +
  9. Explain the storage-safety choices
  10. +
+
forge test --force
+
No upgrade runs until tests and layout validation pass.
+
+ +
+ +
+
Act 3 · V2 reveal
+

Prove state survived

+
make upgrade-v2
make demo-transfer
+
    +
  1. Owner upgrades proxy to BankV2
  2. +
  3. Proxy address remains unchanged
  4. +
  5. Alice transfers 250 to Bob internally
  6. +
  7. No MockUSDC moves during transfer
  8. +
+
+ Console proves
+ Alice: 900 → 650
+ Bob: 500 → 750
+ Reserves = liabilities = 1,400
+ Version: V2 +
+
+
+ +
+
+ Failure behavior + +
+
+ Optional public encore + +
+
+ +
+
+
+

Demo sequence approved

Continue to testing, documentation, and presentation safeguards.

+
+
+
+

Needs changes

Select this and describe what should change in the terminal.

+
+
diff --git a/.superpowers/brainstorm/10582-1786998923/content/waiting-contract-model.html b/.superpowers/brainstorm/10582-1786998923/content/waiting-contract-model.html new file mode 100644 index 0000000..431095f --- /dev/null +++ b/.superpowers/brainstorm/10582-1786998923/content/waiting-contract-model.html @@ -0,0 +1,6 @@ +
+
+

Architecture approved

+

Continuing in the terminal with the contract model, safety rules, and teaching notes…

+
+
diff --git a/.superpowers/brainstorm/10582-1786998923/content/waiting-demo-format.html b/.superpowers/brainstorm/10582-1786998923/content/waiting-demo-format.html new file mode 100644 index 0000000..f0f473a --- /dev/null +++ b/.superpowers/brainstorm/10582-1786998923/content/waiting-demo-format.html @@ -0,0 +1,6 @@ +
+
+

Operations console selected

+

Continuing in the terminal to shape the live Codex demonstration…

+
+
diff --git a/.superpowers/brainstorm/10582-1786998923/content/waiting-final-design.html b/.superpowers/brainstorm/10582-1786998923/content/waiting-final-design.html new file mode 100644 index 0000000..18b15df --- /dev/null +++ b/.superpowers/brainstorm/10582-1786998923/content/waiting-final-design.html @@ -0,0 +1,6 @@ +
+
+

Demo sequence approved

+

Continuing in the terminal with the final design section: safe setup, testing, and learning materials…

+
+
diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/progress.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/progress.md new file mode 100644 index 0000000..3c70b10 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/progress.md @@ -0,0 +1,86 @@ +# SDD ledger — plan: /home/golem/dev/sandbox/codius-test-uupl-smart-contract/.worktrees/uups-bank-demo/docs/superpowers/plans/2026-08-17-uups-bank-demo.md + +Setup: isolated worktree `/home/golem/dev/sandbox/codius-test-uupl-smart-contract/.worktrees/uups-bank-demo` on branch `feature/uups-bank-demo`. +Baseline: clean documentation-only repository; no package manifest or runnable test suite exists before Task 1. +Task 1: fix round 1/5 (1 addressed, 1 open — plan-file scope conflict awaiting user ruling; commits eece34c..a6f9533) +Task 1: ruling — keep the plan correction; the task-specific verified-compatibility clause governs the general listed-files constraint. +Task 1: complete (commits 14980e6..a6f9533, review findings resolved) +Task 2: minor (deferred): standardize MockUSDC public NatSpec to the exact global educational warning. +Task 2: fix round 1/5 (1 addressed, 0 open — literal RED/GREEN command evidence reproduced; no new commit) +Task 2: controller check — reported pre-production missing-source failure and isolated reproduction substantiate TDD chronology; no real gap found. +Task 2: complete (commits a6f9533..1f4175b, review clean; 1 minor deferred) +Task 3: awaiting user ruling — pinned upgrades-core rejects OpenZeppelin 5.6.1 ReentrancyGuard's inherited constructor despite its `@custom:stateless` marker. +Task 3: ruling — user approved the single `unsafe-allow-reachable constructor` annotation; all other upgrade validation remains enabled. +Task 3: approved reachable annotation proved ineffective — upgrades-core 1.46.0 applies it only to opcode traversal, not inherited constructor errors; awaiting second ruling. +Task 3: ruling — user selected OpenZeppelin `ReentrancyGuardTransient`; retain the original sole constructor annotation and add no validator bypass. +Task 3: controller check — report confirms literal RED/GREEN, 19/19 full-suite, and exact storage output; focused source scan confirms no validator bypass, while chain allowlisting remains a later script-task requirement. +Task 3: complete (commits 1f4175b..0664eb4, review clean) +Task 4: controller check — prior fixture confirms proxy binding; foundry config confirms Cancun/fixed seed/validation settings; report records literal offline RED/GREEN commands. +Task 4: complete (commits 0664eb4..2a21766, review clean) +Task 5: minor (deferred): make `actorAt` reject indices above three instead of silently returning actor3. +Task 5: fix round 1/5 (1 addressed, 0 open — genuine handler-absent RED and fresh selector-summary GREEN reproduced; no new commit) +Task 5: complete (commits 2a21766..6dcbb03, review clean; 1 minor deferred) +Task 6: PAUSED by user for host reboot at committed HEAD `6dcbb03`; active worker interrupted. Uncommitted work is limited to `script/lib/DemoScript.sol`, `script/DeployV1.s.sol`, `script/SeedV1Demo.s.sol`, `script/CheckState.s.sol`, and `test/ScriptPreflight.t.sol`. Resume Task 6 from focused preflight GREEN/debug; no Anvil process was running. +Task 6: reboot recovery — focused `forge test --match-path test/ScriptPreflight.t.sol -vvv --force` is GREEN (14/14); preserved work is internally coherent and ready for the remaining Node finalizer/selector, Make targets, and live smoke. + +## Preflight compatibility scan (restored after reboot) + +The approved design spec is authoritative. “Shared interface” below means a direct producer/consumer dependency or a file touched by both tasks, not a merely transitive use of the pinned toolchain. + +| Tasks | Producer / consumer or shared file | Finding / ruling | +| --- | --- | --- | +| 1 / 6 | `Makefile`, Foundry/npm pins, upgrades CLI | Compatible: Task 6 extends the exact pinned foundation and offline validator contract. | +| 1 / 7 | `Makefile`, web configuration and package scripts | Compatible: Task 7 adds bridge targets without changing exact dependency pins. | +| 1 / 8 | `web/src/test/setup.ts`, web scripts | Compatible: Task 8 consumes the configured jsdom/Vitest/React setup. | +| 1 / 9 | `Makefile`, `tools/doctor.sh` | Compatible: Task 9 replaces the temporary doctor and completes the gate. | +| 1 / 11 | `Makefile`, generated artifact policy | Compatible: V2 extends the existing gate and ignored generated module. | +| 1 / 12 | `.env.example`, `Makefile` | Compatible: public Base configuration is added later without secrets or floating dependencies. | +| 2 / 3 | `MockUSDC` asset interface | Compatible: V1 stores and reads the fixed six-decimal ERC-20. | +| 2 / 4 | `MockUSDC` and ERC-20 behavior | Compatible: custody tests build on standard mint/transfer/approve behavior. | +| 2 / 5 | Mintable test liquidity | Compatible: handler receives token ownership only in invariant setup. | +| 2 / 6 | Deployment/seeding asset | Compatible: scripts deploy and seed the same mock token. | +| 2 / 7 | Token ABI and reserve reads | Compatible: ABI sync exports `balanceOf`; browser reads proxy token reserves. | +| 3 / 4 | `src/BankV1.sol` | Compatible: Task 4 adds mutations without changing frozen field order or admin behavior. | +| 3 / 5 | `BankTestBase`, V1 proxy | Compatible: invariants target the validated proxy fixture. | +| 3 / 6 | V1 deploy/init/view interfaces | Compatible: scripts consume only established proxy-facing APIs. | +| 3 / 7 | V1 ABI and state views | Compatible: browser model remains V1-only through `demo-start`. | +| 3 / 10 | V1 storage/layout and UUPS authorization | Compatible subject to the recorded user ruling selecting `ReentrancyGuardTransient`; V2 must inherit the resulting actual layout and add no storage. | +| 4 / 5 | Deposit/withdraw accounting | Compatible: ghost counters independently model exact successful deltas and surplus donations. | +| 4 / 6 | Deposit/withdraw and Act 1 values | Compatible: scripted sequence yields the exact tested accounting state. | +| 4 / 10 | V1 behavior retained through V2 | Compatible: upgrade regression explicitly re-exercises V1 mutations. | +| 5 / 11 | `test/BankInvariant.t.sol`, handler interface | Compatible: Task 11 adds transfers without altering liability/reserve ghosts. | +| 6 / 7 | Confirmed/active manifest contract | Compatible: ABI sync is chain-independent; publishing alone consumes validated `active.json`. | +| 6 / 8 | Dashboard deployment identity and Act 1 snapshot | Compatible: UI consumes only validated manifest/state, never a signer. | +| 6 / 9 | `Makefile`, scripts, manifests, process lifecycle | Compatible: orchestration composes Task 6’s direct targets and preserves Base state during local reset. | +| 6 / 11 | Upgrade/check scripts, finalizer, manifest identity | Compatible: upgrade mutates only implementation/version and stages before confirmed writes. | +| 6 / 12 | Base script paths, finalizer/selector, manifest schema | Ruling: Task 6 provides chain recognition and generic public manifest validation; Task 12 owns the fully runnable Base actor/RPC/keystore path. Cost if wrong: Task 6 may need a narrow compatibility adjustment when Task 12 adds Base actors. | +| 7 / 8 | Dashboard types, manifest parser, bank client | Compatible: Task 8 renders the typed, V1-only read model. | +| 7 / 9 | ABI sync/publish targets and offline verification | Compatible: `make verify` requires ABI freshness but no live manifest. | +| 7 / 11 | `dashboard.ts`, `bankClient`, artifact sync | Compatible: V2 ABI/activity is deliberately absent at `demo-start` and added only after Task 10. | +| 7 / 12 | manifest publisher/selector and Base explorer/RPC metadata | Compatible: browser receives only public credential-free RPC data. | +| 8 / 9 | V1 console and local orchestration | Compatible: Task 9 launches Vite directly and verifies read-only Act 1 display. | +| 8 / 11 | activity timeline and App tests | Compatible: Task 11 adds one transfer variant while retaining read-only UI constraints. | +| 8 / 12 | generic explorer links and Base manifest | Compatible: links derive only from validated confirmed public metadata. | +| 9 / 10 | `demo-start` checkpoint | Compatible: Task 10 begins after the verified V1 tag and does not rewrite it. | +| 9 / 11 | `Makefile`, scan/process gates, prepared V1 state | Compatible: Task 11 verifies before broadcast and runs from the V1 checkpoint. | +| 9 / 12 | `Makefile`, README/runbook, Base-preserving reset | Compatible: the encore remains optional and offline config tests alone join `verify`. | +| 9 / 13 | README/guides, full smoke and tags | Compatible: Task 13 completes claims only from newly observed output. | +| 10 / 11 | `BankV2`, upgrade validation, V2 transfer ABI | Compatible: scripts/UI consume the tested storage-free extension. | +| 10 / 12 | V2 upgrade/transfer on Base | Compatible: Task 12 reuses the same V2 implementation with explicit actors/signing. | +| 10 / 13 | upgrade proof and V2 learning material | Compatible: documentation is conditioned on final verification evidence. | +| 11 / 12 | upgrade/transfer/check scripts, finalizer, `Makefile` | Compatible: Base branches extend local exact paths without weakening local guards. | +| 11 / 13 | Act 2/3 state and event evidence | Compatible: docs consume exact verified proxy/implementation/balance results. | +| 12 / 13 | README/runbook and optional encore | Compatible: no public-network execution is required for local completion. | +| 1 self | Exact versions, install, RED/GREEN web scaffold, initial gate | Internally consistent; prior compatibility exceptions are committed and recorded. | +| 2 self | Token tests then minimal token | Internally consistent; deferred NatSpec wording is non-load-bearing and final scan/docs can normalize it. | +| 3 self | Admin tests, initializer, storage, validator | Ruling already recorded: use `ReentrancyGuardTransient` because pinned validator rejects the inherited ordinary constructor. Cost if wrong: transient-storage behavior would need replacement and revalidation. | +| 4 self | Adversarial tests and exact custody mutations | Internally consistent; rollback and CEI expectations match implementations. | +| 5 self | Three bounded handler actions and three invariants | Internally consistent; deferred `actorAt` bounds check is non-load-bearing because callers use `actorCount`. | +| 6 self | Preflight, staging, finalization, exact local smoke | Internally consistent after ruling Base’s fully configured path belongs to Task 12; confirmed files remain untouched until receipt/code validation. | +| 7 self | Separate ABI sync and manifest publishing | Internally consistent: static verification can run without RPC/manifest while live publishing remains explicit. | +| 8 self | Hook states, console regions, read-only prohibition | Internally consistent; all unknown/failure values remain nonzero-fabricating. | +| 9 self | Process ownership, reset scope, V1 docs/tag | Internally consistent; attached processes and second-terminal smoke are compatible with bounded lifecycle checks. | +| 10 self | Storage-free V2, unit/fuzz/upgrade failures | Internally consistent with the actual V1 layout and no unsafe validation bypass. | +| 11 self | V2 invariants, staging/finalizer, scripts, UI | Internally consistent: already-V2 is a non-broadcast no-op, satisfying the spec’s refusal to send a transaction. | +| 12 self | Explicit optional Base encore | Internally consistent: only offline fake-value config tests enter the mandatory gate; real network use requires explicit user choice. | +| 13 self | Final docs, static gate, three-act smoke, cleanup/tag | Internally consistent; documentation claims are gated on fresh observed evidence. | diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-0664eb4..2a21766.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-0664eb4..2a21766.diff new file mode 100644 index 0000000..5fd19e7 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-0664eb4..2a21766.diff @@ -0,0 +1,569 @@ +# Review package: 0664eb4688bd236e2678c5dda2f4a10e898205ed..2a21766237a9c29970519f7fa00482faecd06fdd + +## Commits +2a21766 feat: add V1 custody accounting + +## Files changed + src/BankV1.sol | 34 ++++ + test/BankV1.t.sol | 326 ++++++++++++++++++++++++++++++++++++++ + test/mocks/FeeOnTransferToken.sol | 26 +++ + test/mocks/ReentrantToken.sol | 104 ++++++++++++ + 4 files changed, 490 insertions(+) + +## Diff +diff --git a/src/BankV1.sol b/src/BankV1.sol +index 13ffca5..0f55426 100644 +--- a/src/BankV1.sol ++++ b/src/BankV1.sol +@@ -1,22 +1,31 @@ + // SPDX-License-Identifier: MIT + pragma solidity 0.8.35; + + import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; ++import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; + import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; + import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; + + contract BankV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardTransient { ++ using SafeERC20 for IERC20; ++ + error InvalidAsset(address asset); ++ error ZeroAmount(); ++ error InsufficientBalance(address account, uint256 available, uint256 requested); ++ error UnexpectedAssetDelta(uint256 expected, uint256 actual); ++ ++ event Deposited(address indexed account, uint256 amount); ++ event Withdrawn(address indexed account, uint256 amount); + + IERC20 internal _asset; + mapping(address account => uint256 balance) internal _balances; + uint256 internal _totalLiabilities; + uint256[47] private __gap; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } +@@ -33,20 +42,45 @@ contract BankV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable, PausableU + } + + function pause() external onlyOwner { + _pause(); + } + + function unpause() external onlyOwner { + _unpause(); + } + ++ function deposit(uint256 amount) external whenNotPaused nonReentrant { ++ if (amount == 0) revert ZeroAmount(); ++ ++ uint256 reservesBefore = _asset.balanceOf(address(this)); ++ _asset.safeTransferFrom(msg.sender, address(this), amount); ++ uint256 reservesAfter = _asset.balanceOf(address(this)); ++ uint256 received = reservesAfter >= reservesBefore ? reservesAfter - reservesBefore : 0; ++ if (received != amount) revert UnexpectedAssetDelta(amount, received); ++ ++ _balances[msg.sender] += amount; ++ _totalLiabilities += amount; ++ emit Deposited(msg.sender, amount); ++ } ++ ++ function withdraw(uint256 amount) external whenNotPaused nonReentrant { ++ if (amount == 0) revert ZeroAmount(); ++ uint256 available = _balances[msg.sender]; ++ if (amount > available) revert InsufficientBalance(msg.sender, available, amount); ++ ++ _balances[msg.sender] = available - amount; ++ _totalLiabilities -= amount; ++ _asset.safeTransfer(msg.sender, amount); ++ emit Withdrawn(msg.sender, amount); ++ } ++ + function asset() external view returns (IERC20) { + return _asset; + } + + function balanceOf(address account) external view returns (uint256) { + return _balances[account]; + } + + function totalLiabilities() external view returns (uint256) { + return _totalLiabilities; +diff --git a/test/BankV1.t.sol b/test/BankV1.t.sol +new file mode 100644 +index 0000000..e53edc1 +--- /dev/null ++++ b/test/BankV1.t.sol +@@ -0,0 +1,326 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; ++import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; ++import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; ++import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; ++ ++import {BankV1} from "../src/BankV1.sol"; ++import {BankTestBase} from "./helpers/BankTestBase.sol"; ++import {FeeOnTransferToken} from "./mocks/FeeOnTransferToken.sol"; ++import {ReentrantToken} from "./mocks/ReentrantToken.sol"; ++ ++event Deposited(address indexed account, uint256 amount); ++event Withdrawn(address indexed account, uint256 amount); ++ ++error ZeroAmount(); ++error InsufficientBalance(address account, uint256 available, uint256 requested); ++error UnexpectedAssetDelta(uint256 expected, uint256 actual); ++ ++contract BankV1CustodyTest is BankTestBase { ++ uint256 private constant MAX_DEPOSIT = 1_000_000e6; ++ ++ function testDepositCreditsExactCustomerAndLiabilityAgainstReceivedReserves() public { ++ _mintAndApprove(alice, 1_000e6, 100e6); ++ ++ vm.prank(alice); ++ bank.deposit(100e6); ++ ++ assertEq(token.balanceOf(alice), 900e6); ++ assertEq(token.balanceOf(proxy), 100e6); ++ assertEq(bank.balanceOf(alice), 100e6); ++ assertEq(bank.totalLiabilities(), 100e6); ++ } ++ ++ function testDepositEmitsDepositedEvent() public { ++ _mintAndApprove(alice, 100e6, 100e6); ++ ++ vm.expectEmit(true, false, false, true, proxy); ++ emit Deposited(alice, 100e6); ++ vm.prank(alice); ++ bank.deposit(100e6); ++ } ++ ++ function testDepositRejectsZeroAmount() public { ++ vm.prank(alice); ++ vm.expectRevert(ZeroAmount.selector); ++ bank.deposit(0); ++ } ++ ++ function testDepositRejectsCallsWhilePaused() public { ++ _mintAndApprove(alice, 100e6, 100e6); ++ vm.prank(owner); ++ bank.pause(); ++ ++ vm.prank(alice); ++ vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); ++ bank.deposit(100e6); ++ } ++ ++ function testDepositRollsBackWhenAllowanceIsInadequate() public { ++ _mintAndApprove(alice, 100e6, 99e6); ++ ++ vm.prank(alice); ++ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, proxy, 99e6, 100e6)); ++ bank.deposit(100e6); ++ ++ _assertEmptyAccounting(alice); ++ assertEq(token.balanceOf(alice), 100e6); ++ } ++ ++ function testDepositRollsBackWhenWalletBalanceIsInadequate() public { ++ _mintAndApprove(alice, 99e6, 100e6); ++ ++ vm.prank(alice); ++ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 99e6, 100e6)); ++ bank.deposit(100e6); ++ ++ _assertEmptyAccounting(alice); ++ assertEq(token.balanceOf(alice), 99e6); ++ } ++ ++ function testDepositsKeepTwoCustomersAccountingIndependent() public { ++ _mintAndApprove(alice, 1_000e6, 300e6); ++ _mintAndApprove(bob, 1_000e6, 700e6); ++ ++ vm.prank(alice); ++ bank.deposit(300e6); ++ vm.prank(bob); ++ bank.deposit(700e6); ++ ++ assertEq(bank.balanceOf(alice), 300e6); ++ assertEq(bank.balanceOf(bob), 700e6); ++ assertEq(bank.totalLiabilities(), 1_000e6); ++ assertEq(token.balanceOf(proxy), 1_000e6); ++ } ++ ++ function testFeeOnTransferDepositRevertsAndRollsBackTokenAndAccounting() public { ++ FeeOnTransferToken feeToken = new FeeOnTransferToken(); ++ BankV1 feeBank = _deployBank(address(feeToken)); ++ feeToken.mint(alice, 100e6); ++ vm.prank(alice); ++ feeToken.approve(address(feeBank), 100e6); ++ ++ vm.prank(alice); ++ vm.expectRevert(abi.encodeWithSelector(UnexpectedAssetDelta.selector, 100e6, 99e6)); ++ feeBank.deposit(100e6); ++ ++ assertEq(feeToken.balanceOf(alice), 100e6); ++ assertEq(feeToken.balanceOf(address(feeBank)), 0); ++ assertEq(feeBank.balanceOf(alice), 0); ++ assertEq(feeBank.totalLiabilities(), 0); ++ } ++ ++ function testDepositSwallowsNestedRevertAndCreditsOnlyOnce() public { ++ ReentrantToken reentrantToken = new ReentrantToken(); ++ BankV1 reentrantBank = _deployBank(address(reentrantToken)); ++ reentrantToken.mint(alice, 100e6); ++ vm.prank(alice); ++ reentrantToken.approve(address(reentrantBank), 100e6); ++ reentrantToken.configureDepositCallback(address(reentrantBank), false); ++ ++ vm.prank(alice); ++ reentrantBank.deposit(100e6); ++ ++ assertTrue(reentrantToken.nestedCallAttempted()); ++ assertFalse(reentrantToken.nestedCallSucceeded()); ++ assertEq(reentrantToken.nestedRevertSelector(), ReentrancyGuardTransient.ReentrancyGuardReentrantCall.selector); ++ assertEq(reentrantToken.observedAccountBalance(), 0); ++ assertEq(reentrantToken.observedLiabilities(), 0); ++ assertEq(reentrantToken.balanceOf(alice), 0); ++ assertEq(reentrantToken.balanceOf(address(reentrantBank)), 100e6); ++ assertEq(reentrantBank.balanceOf(alice), 100e6); ++ assertEq(reentrantBank.totalLiabilities(), 100e6); ++ } ++ ++ function testDepositPropagatesNestedRevertAtomicallyWhenConfigured() public { ++ ReentrantToken reentrantToken = new ReentrantToken(); ++ BankV1 reentrantBank = _deployBank(address(reentrantToken)); ++ reentrantToken.mint(alice, 100e6); ++ vm.prank(alice); ++ reentrantToken.approve(address(reentrantBank), 100e6); ++ reentrantToken.configureDepositCallback(address(reentrantBank), true); ++ ++ vm.prank(alice); ++ vm.expectRevert(ReentrancyGuardTransient.ReentrancyGuardReentrantCall.selector); ++ reentrantBank.deposit(100e6); ++ ++ assertEq(reentrantToken.balanceOf(alice), 100e6); ++ assertEq(reentrantToken.balanceOf(address(reentrantBank)), 0); ++ assertEq(reentrantBank.balanceOf(alice), 0); ++ assertEq(reentrantBank.totalLiabilities(), 0); ++ } ++ ++ function testWithdrawDebitsExactCustomerLiabilityAndReserves() public { ++ _deposit(alice, 1_000e6); ++ ++ vm.prank(alice); ++ bank.withdraw(400e6); ++ ++ assertEq(token.balanceOf(alice), 400e6); ++ assertEq(token.balanceOf(proxy), 600e6); ++ assertEq(bank.balanceOf(alice), 600e6); ++ assertEq(bank.totalLiabilities(), 600e6); ++ } ++ ++ function testWithdrawEmitsWithdrawnEvent() public { ++ _deposit(alice, 100e6); ++ ++ vm.expectEmit(true, false, false, true, proxy); ++ emit Withdrawn(alice, 40e6); ++ vm.prank(alice); ++ bank.withdraw(40e6); ++ } ++ ++ function testWithdrawRejectsZeroAmount() public { ++ vm.prank(alice); ++ vm.expectRevert(ZeroAmount.selector); ++ bank.withdraw(0); ++ } ++ ++ function testWithdrawRejectsCallsWhilePaused() public { ++ _deposit(alice, 100e6); ++ vm.prank(owner); ++ bank.pause(); ++ ++ vm.prank(alice); ++ vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); ++ bank.withdraw(100e6); ++ } ++ ++ function testWithdrawReportsAvailableAndRequestedOnInsufficientInternalBalance() public { ++ _deposit(alice, 40e6); ++ ++ vm.prank(alice); ++ vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector, alice, 40e6, 41e6)); ++ bank.withdraw(41e6); ++ ++ assertEq(bank.balanceOf(alice), 40e6); ++ assertEq(bank.totalLiabilities(), 40e6); ++ assertEq(token.balanceOf(proxy), 40e6); ++ } ++ ++ function testWithdrawUpdatesAccountingBeforeTransferCallbackAndCannotDoubleDebit() public { ++ ReentrantToken reentrantToken = new ReentrantToken(); ++ BankV1 reentrantBank = _deployBank(address(reentrantToken)); ++ reentrantToken.mint(alice, 100e6); ++ vm.prank(alice); ++ reentrantToken.approve(address(reentrantBank), 100e6); ++ vm.prank(alice); ++ reentrantBank.deposit(100e6); ++ reentrantToken.configureWithdrawalCallback(address(reentrantBank), false); ++ ++ vm.prank(alice); ++ reentrantBank.withdraw(40e6); ++ ++ assertEq(reentrantToken.observedAccountBalance(), 60e6); ++ assertEq(reentrantToken.observedLiabilities(), 60e6); ++ assertTrue(reentrantToken.nestedCallAttempted()); ++ assertFalse(reentrantToken.nestedCallSucceeded()); ++ assertEq(reentrantToken.nestedRevertSelector(), ReentrancyGuardTransient.ReentrancyGuardReentrantCall.selector); ++ assertEq(reentrantToken.balanceOf(alice), 40e6); ++ assertEq(reentrantToken.balanceOf(address(reentrantBank)), 60e6); ++ assertEq(reentrantBank.balanceOf(alice), 60e6); ++ assertEq(reentrantBank.totalLiabilities(), 60e6); ++ } ++ ++ function testOneCustomersWithdrawalLeavesOtherCustomerUnchanged() public { ++ _deposit(alice, 100e6); ++ _deposit(bob, 200e6); ++ ++ vm.prank(alice); ++ bank.withdraw(40e6); ++ ++ assertEq(bank.balanceOf(alice), 60e6); ++ assertEq(bank.balanceOf(bob), 200e6); ++ assertEq(bank.totalLiabilities(), 260e6); ++ assertEq(token.balanceOf(proxy), 260e6); ++ } ++ ++ function testDirectTransferCreatesSurplusThatRemainsAfterFullWithdrawal() public { ++ _deposit(alice, 100e6); ++ vm.prank(owner); ++ token.mint(bob, 25e6); ++ vm.prank(bob); ++ token.transfer(proxy, 25e6); ++ ++ assertEq(token.balanceOf(proxy), 125e6); ++ assertEq(bank.totalLiabilities(), 100e6); ++ ++ vm.prank(alice); ++ bank.withdraw(100e6); ++ ++ assertEq(token.balanceOf(proxy), 25e6); ++ assertEq(bank.balanceOf(alice), 0); ++ assertEq(bank.totalLiabilities(), 0); ++ } ++ ++ function testFuzzDepositPreservesExactAccounting(uint256 amountSeed) public { ++ uint256 amount = bound(amountSeed, 1, MAX_DEPOSIT); ++ _mintAndApprove(alice, amount, amount); ++ ++ vm.prank(alice); ++ bank.deposit(amount); ++ ++ assertEq(token.balanceOf(alice), 0); ++ assertEq(token.balanceOf(proxy), amount); ++ assertEq(bank.balanceOf(alice), amount); ++ assertEq(bank.totalLiabilities(), amount); ++ } ++ ++ function testFuzzWithdrawPreservesExactAccounting(uint256 depositSeed, uint256 withdrawalSeed) public { ++ uint256 deposited = bound(depositSeed, 1, MAX_DEPOSIT); ++ uint256 withdrawn = bound(withdrawalSeed, 1, deposited); ++ _deposit(alice, deposited); ++ ++ vm.prank(alice); ++ bank.withdraw(withdrawn); ++ ++ uint256 remaining = deposited - withdrawn; ++ assertEq(token.balanceOf(alice), withdrawn); ++ assertEq(token.balanceOf(proxy), remaining); ++ assertEq(bank.balanceOf(alice), remaining); ++ assertEq(bank.totalLiabilities(), remaining); ++ } ++ ++ function testFuzzOverWithdrawAlwaysReverts(uint256 depositSeed, uint256 excessSeed) public { ++ uint256 deposited = bound(depositSeed, 1, MAX_DEPOSIT); ++ uint256 excess = bound(excessSeed, 1, MAX_DEPOSIT); ++ uint256 requested = deposited + excess; ++ _deposit(alice, deposited); ++ ++ vm.prank(alice); ++ vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector, alice, deposited, requested)); ++ bank.withdraw(requested); ++ ++ assertEq(bank.balanceOf(alice), deposited); ++ assertEq(bank.totalLiabilities(), deposited); ++ assertEq(token.balanceOf(proxy), deposited); ++ } ++ ++ function _mintAndApprove(address account, uint256 mintAmount, uint256 approveAmount) private { ++ vm.prank(owner); ++ token.mint(account, mintAmount); ++ vm.prank(account); ++ token.approve(proxy, approveAmount); ++ } ++ ++ function _deposit(address account, uint256 amount) private { ++ _mintAndApprove(account, amount, amount); ++ vm.prank(account); ++ bank.deposit(amount); ++ } ++ ++ function _deployBank(address asset_) private returns (BankV1 deployedBank) { ++ address deployedProxy = ++ Upgrades.deployUUPSProxy("BankV1.sol:BankV1", abi.encodeCall(BankV1.initialize, (asset_, owner))); ++ deployedBank = BankV1(deployedProxy); ++ } ++ ++ function _assertEmptyAccounting(address account) private view { ++ assertEq(token.balanceOf(proxy), 0); ++ assertEq(bank.balanceOf(account), 0); ++ assertEq(bank.totalLiabilities(), 0); ++ } ++} +diff --git a/test/mocks/FeeOnTransferToken.sol b/test/mocks/FeeOnTransferToken.sol +new file mode 100644 +index 0000000..80f5280 +--- /dev/null ++++ b/test/mocks/FeeOnTransferToken.sol +@@ -0,0 +1,26 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; ++ ++contract FeeOnTransferToken is ERC20 { ++ constructor() ERC20("Fee-on-Transfer Token", "FOT") {} ++ ++ function decimals() public pure override returns (uint8) { ++ return 6; ++ } ++ ++ function mint(address to, uint256 amount) external { ++ _mint(to, amount); ++ } ++ ++ function transferFrom(address from, address to, uint256 amount) public override returns (bool) { ++ address spender = _msgSender(); ++ _spendAllowance(from, spender, amount); ++ ++ uint256 received = amount * 99 / 100; ++ _transfer(from, to, received); ++ _burn(from, amount - received); ++ return true; ++ } ++} +diff --git a/test/mocks/ReentrantToken.sol b/test/mocks/ReentrantToken.sol +new file mode 100644 +index 0000000..98dabf3 +--- /dev/null ++++ b/test/mocks/ReentrantToken.sol +@@ -0,0 +1,104 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; ++ ++interface IReentrantBankTarget { ++ function deposit(uint256 amount) external; ++ function withdraw(uint256 amount) external; ++ function balanceOf(address account) external view returns (uint256); ++ function totalLiabilities() external view returns (uint256); ++} ++ ++contract ReentrantToken is ERC20 { ++ enum Callback { ++ None, ++ Deposit, ++ Withdraw ++ } ++ ++ IReentrantBankTarget public callbackTarget; ++ Callback public callback; ++ bool public propagateRevert; ++ bool public nestedCallAttempted; ++ bool public nestedCallSucceeded; ++ bytes4 public nestedRevertSelector; ++ uint256 public observedAccountBalance; ++ uint256 public observedLiabilities; ++ ++ constructor() ERC20("Reentrant Token", "REENT") {} ++ ++ function decimals() public pure override returns (uint8) { ++ return 6; ++ } ++ ++ function mint(address to, uint256 amount) external { ++ _mint(to, amount); ++ } ++ ++ function configureDepositCallback(address bank, bool propagate) external { ++ callbackTarget = IReentrantBankTarget(bank); ++ callback = Callback.Deposit; ++ propagateRevert = propagate; ++ _resetObservations(); ++ } ++ ++ function configureWithdrawalCallback(address bank, bool propagate) external { ++ callbackTarget = IReentrantBankTarget(bank); ++ callback = Callback.Withdraw; ++ propagateRevert = propagate; ++ _resetObservations(); ++ } ++ ++ function clearCallback() external { ++ callback = Callback.None; ++ propagateRevert = false; ++ _resetObservations(); ++ } ++ ++ function transferFrom(address from, address to, uint256 amount) public override returns (bool) { ++ if (callback == Callback.Deposit && _msgSender() == address(callbackTarget)) { ++ observedAccountBalance = callbackTarget.balanceOf(from); ++ observedLiabilities = callbackTarget.totalLiabilities(); ++ _attemptNestedCall(abi.encodeCall(IReentrantBankTarget.deposit, (1))); ++ } ++ return super.transferFrom(from, to, amount); ++ } ++ ++ function transfer(address to, uint256 amount) public override returns (bool) { ++ if (callback == Callback.Withdraw && _msgSender() == address(callbackTarget)) { ++ observedAccountBalance = callbackTarget.balanceOf(to); ++ observedLiabilities = callbackTarget.totalLiabilities(); ++ _attemptNestedCall(abi.encodeCall(IReentrantBankTarget.withdraw, (1))); ++ } ++ return super.transfer(to, amount); ++ } ++ ++ function _attemptNestedCall(bytes memory callData) private { ++ nestedCallAttempted = true; ++ bytes memory revertData; ++ (nestedCallSucceeded, revertData) = address(callbackTarget).call(callData); ++ ++ if (!nestedCallSucceeded && revertData.length >= 4) { ++ bytes4 selector; ++ assembly ("memory-safe") { ++ selector := mload(add(revertData, 0x20)) ++ } ++ nestedRevertSelector = selector; ++ } ++ ++ if (!nestedCallSucceeded && propagateRevert) { ++ assembly ("memory-safe") { ++ revert(add(revertData, 0x20), mload(revertData)) ++ } ++ } ++ } ++ ++ function _resetObservations() private { ++ nestedCallAttempted = false; ++ nestedCallSucceeded = false; ++ nestedRevertSelector = bytes4(0); ++ observedAccountBalance = 0; ++ observedLiabilities = 0; ++ } ++} diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-14980e6..eece34c.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-14980e6..eece34c.diff new file mode 100644 index 0000000..ecf7f73 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-14980e6..eece34c.diff @@ -0,0 +1,6339 @@ +# Review package: 14980e60836f18b3c4890e69246e8800327a4785..eece34c088d52f1650baef34b51a8fa26ec12a00 + +## Commits +eece34c build: resolve TypeScript 7 tooling compatibility +db377c2 build: pin demo toolchains + +## Files changed + .env.example | 1 + + .gitignore | 16 + + .gitmodules | 9 + + .nvmrc | 1 + + Makefile | 19 + + .../superpowers/plans/2026-08-17-uups-bank-demo.md | 4 +- + foundry.lock | 20 + + foundry.toml | 27 + + lib/forge-std | 1 + + lib/openzeppelin-contracts-upgradeable | 1 + + lib/openzeppelin-foundry-upgrades | 1 + + package-lock.json | 1258 ++++++ + package.json | 14 + + remappings.txt | 4 + + tools/check-upgrades-cli.mjs | 28 + + tools/doctor.sh | 13 + + web/eslint.config.js | 26 + + web/index.html | 11 + + web/package-lock.json | 4531 ++++++++++++++++++++ + web/package.json | 41 + + web/src/config/toolchain.ts | 6 + + web/src/test/setup.ts | 1 + + web/src/test/toolchain.test.ts | 13 + + web/tsconfig.app.json | 20 + + web/tsconfig.json | 7 + + web/tsconfig.node.json | 16 + + web/vite.config.ts | 11 + + 27 files changed, 6098 insertions(+), 2 deletions(-) + +## Diff +diff --git a/.env.example b/.env.example +new file mode 100644 +index 0000000..591c6c2 +--- /dev/null ++++ b/.env.example +@@ -0,0 +1 @@ ++# Copy this file to .env for local-only configuration. +diff --git a/.gitignore b/.gitignore +index f927ba5..ab5e232 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,2 +1,18 @@ + .superpowers/ + .worktrees/ ++.env ++.env.local ++.demo/ ++cache/ ++out/ ++broadcast/ ++deployments/*.json ++deployments/**/*.json ++!deployments/*.example.json ++node_modules/ ++web/node_modules/ ++web/dist/ ++web/coverage/ ++web/public/deployment.json ++web/src/generated/*.ts ++!.gitkeep +diff --git a/.gitmodules b/.gitmodules +new file mode 100644 +index 0000000..23acfb1 +--- /dev/null ++++ b/.gitmodules +@@ -0,0 +1,9 @@ ++[submodule "lib/forge-std"] ++ path = lib/forge-std ++ url = https://github.com/foundry-rs/forge-std ++[submodule "lib/openzeppelin-foundry-upgrades"] ++ path = lib/openzeppelin-foundry-upgrades ++ url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades ++[submodule "lib/openzeppelin-contracts-upgradeable"] ++ path = lib/openzeppelin-contracts-upgradeable ++ url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +diff --git a/.nvmrc b/.nvmrc +new file mode 100644 +index 0000000..ca5c350 +--- /dev/null ++++ b/.nvmrc +@@ -0,0 +1 @@ ++24.18.0 +diff --git a/Makefile b/Makefile +new file mode 100644 +index 0000000..00edc2c +--- /dev/null ++++ b/Makefile +@@ -0,0 +1,19 @@ ++SHELL := /bin/bash ++.SHELLFLAGS := -euo pipefail -c ++ ++.PHONY: doctor setup verify ++doctor: ++ @./tools/doctor.sh ++setup: ++ @git submodule update --init --recursive ++ @npm ci ++ @npm --prefix web ci ++verify: ++ @forge fmt --check ++ @forge clean ++ @npm_config_offline=true forge build --force ++ @npm_config_offline=true forge test --force ++ @npm --prefix web run lint ++ @npm --prefix web run typecheck ++ @npm --prefix web test ++ @npm --prefix web run build +diff --git a/docs/superpowers/plans/2026-08-17-uups-bank-demo.md b/docs/superpowers/plans/2026-08-17-uups-bank-demo.md +index 7f88085..3a3ca8f 100644 +--- a/docs/superpowers/plans/2026-08-17-uups-bank-demo.md ++++ b/docs/superpowers/plans/2026-08-17-uups-bank-demo.md +@@ -1,19 +1,19 @@ + # UUPS Bank Demo Implementation Plan + + > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + + **Goal:** Deliver a deterministic, local-first UUPS “bank” demo that starts at a verified V1 checkpoint, adds internal balance transfers in V2 without losing state, and explains the result through a read-only React operations console. + + **Architecture:** Foundry scripts are the only state-changing control plane. A six-decimal `MockUSDC` and a UUPS `BankV1` implementation run behind a stable ERC-1967 proxy on Anvil or optional Base Sepolia. Scripts export a public deployment manifest and contract ABIs; a wagmi/viem client reads those artifacts and renders reserves, liabilities, identities, actors, and events without a signer or wallet connector. V2 inherits V1, adds no storage, and adds one internal transfer function. + +-**Tech Stack:** Foundry `v1.7.1`, forge-std `v1.16.1`, Solidity `0.8.35`, OpenZeppelin Contracts Upgradeable `v5.6.1`, OpenZeppelin Foundry Upgrades `v0.4.1`, OpenZeppelin Upgrades Core `1.46.0`, Node `24.18.0`, npm `11.17.0`, React `19.2.8`, TypeScript `7.0.2`, Vite `8.2.0`, wagmi `3.7.5`, viem `2.55.8`, TanStack Query `5.101.4`, Vitest `4.1.10`, Testing Library React `16.3.2`, and jsdom `30.0.1`. ++**Tech Stack:** Foundry `v1.7.1`, forge-std `v1.16.1`, Solidity `0.8.35`, OpenZeppelin Contracts Upgradeable `v5.6.1`, OpenZeppelin Foundry Upgrades `v0.4.1`, OpenZeppelin Upgrades Core `1.46.0`, Node `24.18.0`, npm `11.17.0`, React `19.2.8`, TypeScript compiler `7.0.2` via `@typescript/native`, TypeScript API compatibility `6.0.2` via the `typescript` alias, Vite `8.2.0`, wagmi `3.7.5`, viem `2.55.8`, TanStack Query `5.101.4`, Vitest `4.1.10`, Testing Library React `16.3.2`, and jsdom `30.0.1`. + + ## Global Constraints + + - The contracts are educational and unaudited. Every user-facing surface says: `Educational demo — mock token — never use real funds.` + - State-changing scripts accept only chain IDs `31337` and `84532`; no mainnet RPC, address, target, or configuration is added. + - Local accounts come only from Anvil’s standard development mnemonic. Testnet signing uses a named encrypted Foundry keystore and `--account`; no supported command accepts a raw private key or mnemonic environment variable. + - Every application call uses the proxy address. The implementation address is read only for validation and explanation. + - `Upgrades`, never `UnsafeUpgrades`, performs deploy/upgrade validation. The sole validator allowance is the constructor annotation immediately above `_disableInitializers()`. + - V2 does not add, delete, reorder, or change the type of any storage variable and has no initializer. + - The browser has no connector, signer, transaction client, or write button. Failed reads remain unknown; they are never rendered as zero. +@@ -145,21 +145,21 @@ web/src/generated/*.ts + "scripts": { + "dev": "vite --host 127.0.0.1", + "lint": "eslint . --max-warnings 0", + "typecheck": "tsc -b --pretty false", + "test": "vitest run", + "build": "tsc -b && vite build" + } + } + ``` + +-Add exact runtime dependencies `@tanstack/react-query@5.101.4`, `react@19.2.8`, `react-dom@19.2.8`, `viem@2.55.8`, and `wagmi@3.7.5`. Add exact dev dependencies `@eslint/js@10.0.1`, `@testing-library/dom@10.4.1`, `@testing-library/react@16.3.2`, `@types/node@24.10.0`, `@types/react@19.2.14`, `@types/react-dom@19.2.4`, `@vitejs/plugin-react@6.0.4`, `eslint@10.0.1`, `eslint-plugin-react-hooks@7.1.1`, `eslint-plugin-react-refresh@0.5.3`, `globals@17.7.0`, `jsdom@30.0.1`, `typescript@7.0.2`, `typescript-eslint@8.65.0`, `vite@8.2.0`, and `vitest@4.1.10`. If npm rejects one exact revision because the registry changed, verify the official release before changing both this plan and the package file. ++Add exact runtime dependencies `@tanstack/react-query@5.101.4`, `react@19.2.8`, `react-dom@19.2.8`, `viem@2.55.8`, and `wagmi@3.7.5`. Add exact dev dependencies `@eslint/js@10.0.1`, `@testing-library/dom@10.4.1`, `@testing-library/react@16.3.2`, `@types/node@24.10.0`, `@types/react@19.2.14`, `@types/react-dom@19.2.4`, `@typescript/native@npm:typescript@7.0.2`, `@vitejs/plugin-react@6.0.4`, `eslint@10.0.1`, `eslint-plugin-react-hooks@7.1.1`, `eslint-plugin-react-refresh@0.5.3`, `globals@17.7.0`, `jsdom@30.0.1`, `typescript@npm:@typescript/typescript6@6.0.2`, `typescript-eslint@8.65.0`, `vite@8.2.0`, and `vitest@4.1.10`. The TypeScript 7 native compiler ships without the API consumed by typescript-eslint, so install its range-free TypeScript 6 compatibility alias under `typescript` and the range-free TypeScript 7 compiler alias under `@typescript/native`. If npm rejects one exact revision because the registry changed, verify the official release before changing both this plan and the package file. + + - [ ] Configure Foundry in `foundry.toml`: + + ```toml + [profile.default] + src = "src" + test = "test" + script = "script" + out = "out" + libs = ["lib"] +diff --git a/foundry.lock b/foundry.lock +new file mode 100644 +index 0000000..ebdd117 +--- /dev/null ++++ b/foundry.lock +@@ -0,0 +1,20 @@ ++{ ++ "lib/forge-std": { ++ "tag": { ++ "name": "v1.16.1", ++ "rev": "620536fa5277db4e3fd46772d5cbc1ea0696fb43" ++ } ++ }, ++ "lib/openzeppelin-contracts-upgradeable": { ++ "tag": { ++ "name": "v5.6.1", ++ "rev": "7bf4727aacdbfaa0f36cbd664654d0c9e1dc52bf" ++ } ++ }, ++ "lib/openzeppelin-foundry-upgrades": { ++ "tag": { ++ "name": "v0.4.1", ++ "rev": "258e12e727bfe7f0ec30c51995d01ec88b82efc1" ++ } ++ } ++} +\ No newline at end of file +diff --git a/foundry.toml b/foundry.toml +new file mode 100644 +index 0000000..7706bc4 +--- /dev/null ++++ b/foundry.toml +@@ -0,0 +1,27 @@ ++[profile.default] ++src = "src" ++test = "test" ++script = "script" ++out = "out" ++libs = ["lib"] ++solc_version = "0.8.35" ++evm_version = "cancun" ++optimizer = true ++optimizer_runs = 200 ++ffi = true ++ast = true ++build_info = true ++extra_output = ["storageLayout"] ++fs_permissions = [ ++ { access = "read", path = "out" }, ++ { access = "read-write", path = "deployments" } ++] ++ ++[fuzz] ++runs = 512 ++seed = "0x5555505342414e4b" ++ ++[invariant] ++runs = 128 ++depth = 64 ++fail_on_revert = true +diff --git a/lib/forge-std b/lib/forge-std +new file mode 160000 +index 0000000..620536f +--- /dev/null ++++ b/lib/forge-std +@@ -0,0 +1 @@ ++Subproject commit 620536fa5277db4e3fd46772d5cbc1ea0696fb43 +diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable +new file mode 160000 +index 0000000..7bf4727 +--- /dev/null ++++ b/lib/openzeppelin-contracts-upgradeable +@@ -0,0 +1 @@ ++Subproject commit 7bf4727aacdbfaa0f36cbd664654d0c9e1dc52bf +diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades +new file mode 160000 +index 0000000..258e12e +--- /dev/null ++++ b/lib/openzeppelin-foundry-upgrades +@@ -0,0 +1 @@ ++Subproject commit 258e12e727bfe7f0ec30c51995d01ec88b82efc1 +diff --git a/package-lock.json b/package-lock.json +new file mode 100644 +index 0000000..73ed416 +--- /dev/null ++++ b/package-lock.json +@@ -0,0 +1,1258 @@ ++{ ++ "name": "uups-bank-demo", ++ "version": "0.1.0", ++ "lockfileVersion": 3, ++ "requires": true, ++ "packages": { ++ "": { ++ "name": "uups-bank-demo", ++ "version": "0.1.0", ++ "devDependencies": { ++ "@openzeppelin/upgrades-core": "1.46.0" ++ }, ++ "engines": { ++ "node": ">=24.18.0 <25", ++ "npm": ">=11.17.0 <12" ++ } ++ }, ++ "node_modules/@bytecodealliance/preview2-shim": { ++ "version": "0.17.0", ++ "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.0.tgz", ++ "integrity": "sha512-JorcEwe4ud0x5BS/Ar2aQWOQoFzjq/7jcnxYXCvSMh0oRm0dQXzOA+hqLDBnOMks1LLBA7dmiLLsEBl09Yd6iQ==", ++ "dev": true, ++ "license": "(Apache-2.0 WITH LLVM-exception)" ++ }, ++ "node_modules/@nomicfoundation/slang": { ++ "version": "0.18.3", ++ "resolved": "https://registry.npmjs.org/@nomicfoundation/slang/-/slang-0.18.3.tgz", ++ "integrity": "sha512-YqAWgckqbHM0/CZxi9Nlf4hjk9wUNLC9ngWCWBiqMxPIZmzsVKYuChdlrfeBPQyvQQBoOhbx+7C1005kLVQDZQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@bytecodealliance/preview2-shim": "0.17.0" ++ } ++ }, ++ "node_modules/@openzeppelin/upgrades-core": { ++ "version": "1.46.0", ++ "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.46.0.tgz", ++ "integrity": "sha512-UFSeO/4r8eeXj0C/HAwV+J4b72sE1HX0aALQFs5S2RBOsfXvKweyjQf35vrK32LQiyHdP6IPShAsEBVvpSEgGQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@nomicfoundation/slang": "^0.18.3", ++ "bignumber.js": "^9.1.2", ++ "cbor": "^10.0.0", ++ "chalk": "^4.1.0", ++ "compare-versions": "^6.0.0", ++ "debug": "^4.1.1", ++ "ethereumjs-util": "^7.0.3", ++ "minimatch": "^10.2.5", ++ "minimist": "^1.2.7", ++ "proper-lockfile": "^4.1.1", ++ "solidity-ast": "^0.4.60" ++ }, ++ "bin": { ++ "openzeppelin-upgrades-core": "dist/cli/cli.js" ++ } ++ }, ++ "node_modules/@types/bn.js": { ++ "version": "5.2.0", ++ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", ++ "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@types/node": "*" ++ } ++ }, ++ "node_modules/@types/node": { ++ "version": "26.2.0", ++ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", ++ "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "undici-types": "~8.3.0" ++ } ++ }, ++ "node_modules/@types/pbkdf2": { ++ "version": "3.1.2", ++ "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", ++ "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@types/node": "*" ++ } ++ }, ++ "node_modules/@types/secp256k1": { ++ "version": "4.0.7", ++ "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.7.tgz", ++ "integrity": "sha512-Rcvjl6vARGAKRO6jHeKMatGrvOMGrR/AR11N1x2LqintPCyDZ7NBhrh238Z2VZc7aM7KIwnFpFQ7fnfK4H/9Qw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@types/node": "*" ++ } ++ }, ++ "node_modules/ansi-styles": { ++ "version": "4.3.0", ++ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", ++ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "color-convert": "^2.0.1" ++ }, ++ "engines": { ++ "node": ">=8" ++ }, ++ "funding": { ++ "url": "https://github.com/chalk/ansi-styles?sponsor=1" ++ } ++ }, ++ "node_modules/available-typed-arrays": { ++ "version": "1.0.7", ++ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", ++ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "possible-typed-array-names": "^1.0.0" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/balanced-match": { ++ "version": "4.0.4", ++ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", ++ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "18 || 20 || >=22" ++ } ++ }, ++ "node_modules/base-x": { ++ "version": "3.0.11", ++ "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", ++ "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "safe-buffer": "^5.0.1" ++ } ++ }, ++ "node_modules/bignumber.js": { ++ "version": "9.3.1", ++ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", ++ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "*" ++ } ++ }, ++ "node_modules/blakejs": { ++ "version": "1.2.1", ++ "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", ++ "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/bn.js": { ++ "version": "5.2.5", ++ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", ++ "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/brace-expansion": { ++ "version": "5.0.9", ++ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", ++ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "balanced-match": "^4.0.2" ++ }, ++ "engines": { ++ "node": "20 || >=22" ++ } ++ }, ++ "node_modules/brorand": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", ++ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/browserify-aes": { ++ "version": "1.2.0", ++ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", ++ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "buffer-xor": "^1.0.3", ++ "cipher-base": "^1.0.0", ++ "create-hash": "^1.1.0", ++ "evp_bytestokey": "^1.0.3", ++ "inherits": "^2.0.1", ++ "safe-buffer": "^5.0.1" ++ } ++ }, ++ "node_modules/bs58": { ++ "version": "4.0.1", ++ "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", ++ "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "base-x": "^3.0.2" ++ } ++ }, ++ "node_modules/bs58check": { ++ "version": "2.1.2", ++ "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", ++ "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "bs58": "^4.0.0", ++ "create-hash": "^1.1.0", ++ "safe-buffer": "^5.1.2" ++ } ++ }, ++ "node_modules/buffer-xor": { ++ "version": "1.0.3", ++ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", ++ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/call-bind": { ++ "version": "1.0.9", ++ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", ++ "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", ++ "dev": true, ++ "license": "MIT", ++ "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" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/call-bind-apply-helpers": { ++ "version": "1.0.2", ++ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", ++ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "es-errors": "^1.3.0", ++ "function-bind": "^1.1.2" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/call-bound": { ++ "version": "1.0.4", ++ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", ++ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "call-bind-apply-helpers": "^1.0.2", ++ "get-intrinsic": "^1.3.0" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/cbor": { ++ "version": "10.0.12", ++ "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.12.tgz", ++ "integrity": "sha512-exQDevYd7ZQLP4moMQcZkKCVZsXLAtUSflObr3xTh4xzFIv/xBCdvCd6L259kQOUP2kcTC0jvC6PpZIf/WmRXA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "nofilter": "^3.0.2" ++ }, ++ "engines": { ++ "node": ">=20" ++ } ++ }, ++ "node_modules/chalk": { ++ "version": "4.1.2", ++ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", ++ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "ansi-styles": "^4.1.0", ++ "supports-color": "^7.1.0" ++ }, ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/chalk/chalk?sponsor=1" ++ } ++ }, ++ "node_modules/cipher-base": { ++ "version": "1.0.7", ++ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", ++ "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "inherits": "^2.0.4", ++ "safe-buffer": "^5.2.1", ++ "to-buffer": "^1.2.2" ++ }, ++ "engines": { ++ "node": ">= 0.10" ++ } ++ }, ++ "node_modules/color-convert": { ++ "version": "2.0.1", ++ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", ++ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "color-name": "~1.1.4" ++ }, ++ "engines": { ++ "node": ">=7.0.0" ++ } ++ }, ++ "node_modules/color-name": { ++ "version": "1.1.4", ++ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", ++ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/compare-versions": { ++ "version": "6.1.1", ++ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", ++ "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/core-util-is": { ++ "version": "1.0.3", ++ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", ++ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/create-hash": { ++ "version": "1.2.0", ++ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", ++ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "cipher-base": "^1.0.1", ++ "inherits": "^2.0.1", ++ "md5.js": "^1.3.4", ++ "ripemd160": "^2.0.1", ++ "sha.js": "^2.4.0" ++ } ++ }, ++ "node_modules/create-hmac": { ++ "version": "1.1.7", ++ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", ++ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "cipher-base": "^1.0.3", ++ "create-hash": "^1.1.0", ++ "inherits": "^2.0.1", ++ "ripemd160": "^2.0.0", ++ "safe-buffer": "^5.0.1", ++ "sha.js": "^2.4.8" ++ } ++ }, ++ "node_modules/debug": { ++ "version": "4.4.3", ++ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", ++ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "ms": "^2.1.3" ++ }, ++ "engines": { ++ "node": ">=6.0" ++ }, ++ "peerDependenciesMeta": { ++ "supports-color": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/define-data-property": { ++ "version": "1.1.4", ++ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", ++ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "es-define-property": "^1.0.0", ++ "es-errors": "^1.3.0", ++ "gopd": "^1.0.1" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/dunder-proto": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", ++ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "call-bind-apply-helpers": "^1.0.1", ++ "es-errors": "^1.3.0", ++ "gopd": "^1.2.0" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/elliptic": { ++ "version": "6.6.1", ++ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", ++ "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "bn.js": "^4.11.9", ++ "brorand": "^1.1.0", ++ "hash.js": "^1.0.0", ++ "hmac-drbg": "^1.0.1", ++ "inherits": "^2.0.4", ++ "minimalistic-assert": "^1.0.1", ++ "minimalistic-crypto-utils": "^1.0.1" ++ } ++ }, ++ "node_modules/elliptic/node_modules/bn.js": { ++ "version": "4.12.5", ++ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", ++ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/es-define-property": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", ++ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/es-errors": { ++ "version": "1.3.0", ++ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", ++ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/es-object-atoms": { ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", ++ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "es-errors": "^1.3.0" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/ethereum-cryptography": { ++ "version": "0.1.3", ++ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", ++ "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@types/pbkdf2": "^3.0.0", ++ "@types/secp256k1": "^4.0.1", ++ "blakejs": "^1.1.0", ++ "browserify-aes": "^1.2.0", ++ "bs58check": "^2.1.2", ++ "create-hash": "^1.2.0", ++ "create-hmac": "^1.1.7", ++ "hash.js": "^1.1.7", ++ "keccak": "^3.0.0", ++ "pbkdf2": "^3.0.17", ++ "randombytes": "^2.1.0", ++ "safe-buffer": "^5.1.2", ++ "scrypt-js": "^3.0.0", ++ "secp256k1": "^4.0.1", ++ "setimmediate": "^1.0.5" ++ } ++ }, ++ "node_modules/ethereumjs-util": { ++ "version": "7.1.5", ++ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", ++ "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", ++ "dev": true, ++ "license": "MPL-2.0", ++ "dependencies": { ++ "@types/bn.js": "^5.1.0", ++ "bn.js": "^5.1.2", ++ "create-hash": "^1.1.2", ++ "ethereum-cryptography": "^0.1.3", ++ "rlp": "^2.2.4" ++ }, ++ "engines": { ++ "node": ">=10.0.0" ++ } ++ }, ++ "node_modules/evp_bytestokey": { ++ "version": "1.0.3", ++ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", ++ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "md5.js": "^1.3.4", ++ "safe-buffer": "^5.1.1" ++ } ++ }, ++ "node_modules/for-each": { ++ "version": "0.3.5", ++ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", ++ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "is-callable": "^1.2.7" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/function-bind": { ++ "version": "1.1.2", ++ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", ++ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", ++ "dev": true, ++ "license": "MIT", ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/get-intrinsic": { ++ "version": "1.3.0", ++ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", ++ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", ++ "dev": true, ++ "license": "MIT", ++ "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" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/get-proto": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", ++ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "dunder-proto": "^1.0.1", ++ "es-object-atoms": "^1.0.0" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/gopd": { ++ "version": "1.2.0", ++ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", ++ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/graceful-fs": { ++ "version": "4.2.11", ++ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", ++ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/has-flag": { ++ "version": "4.0.0", ++ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", ++ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/has-property-descriptors": { ++ "version": "1.0.2", ++ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", ++ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "es-define-property": "^1.0.0" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/has-symbols": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", ++ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/has-tostringtag": { ++ "version": "1.0.2", ++ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", ++ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "has-symbols": "^1.0.3" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/hash-base": { ++ "version": "3.1.2", ++ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", ++ "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "inherits": "^2.0.4", ++ "readable-stream": "^2.3.8", ++ "safe-buffer": "^5.2.1", ++ "to-buffer": "^1.2.1" ++ }, ++ "engines": { ++ "node": ">= 0.8" ++ } ++ }, ++ "node_modules/hash-base/node_modules/isarray": { ++ "version": "1.0.0", ++ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", ++ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/hash-base/node_modules/readable-stream": { ++ "version": "2.3.8", ++ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", ++ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "core-util-is": "~1.0.0", ++ "inherits": "~2.0.3", ++ "isarray": "~1.0.0", ++ "process-nextick-args": "~2.0.0", ++ "safe-buffer": "~5.1.1", ++ "string_decoder": "~1.1.1", ++ "util-deprecate": "~1.0.1" ++ } ++ }, ++ "node_modules/hash-base/node_modules/readable-stream/node_modules/safe-buffer": { ++ "version": "5.1.2", ++ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", ++ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/hash-base/node_modules/string_decoder": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", ++ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "safe-buffer": "~5.1.0" ++ } ++ }, ++ "node_modules/hash-base/node_modules/string_decoder/node_modules/safe-buffer": { ++ "version": "5.1.2", ++ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", ++ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/hash.js": { ++ "version": "1.1.7", ++ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", ++ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "inherits": "^2.0.3", ++ "minimalistic-assert": "^1.0.1" ++ } ++ }, ++ "node_modules/hasown": { ++ "version": "2.0.4", ++ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", ++ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "function-bind": "^1.1.2" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/hmac-drbg": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", ++ "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "hash.js": "^1.0.3", ++ "minimalistic-assert": "^1.0.0", ++ "minimalistic-crypto-utils": "^1.0.1" ++ } ++ }, ++ "node_modules/inherits": { ++ "version": "2.0.4", ++ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", ++ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/is-callable": { ++ "version": "1.2.7", ++ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", ++ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/is-typed-array": { ++ "version": "1.1.15", ++ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", ++ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "which-typed-array": "^1.1.16" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/isarray": { ++ "version": "2.0.5", ++ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", ++ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/keccak": { ++ "version": "3.0.4", ++ "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", ++ "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", ++ "dev": true, ++ "hasInstallScript": true, ++ "license": "MIT", ++ "dependencies": { ++ "node-addon-api": "^2.0.0", ++ "node-gyp-build": "^4.2.0", ++ "readable-stream": "^3.6.0" ++ }, ++ "engines": { ++ "node": ">=10.0.0" ++ } ++ }, ++ "node_modules/math-intrinsics": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", ++ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/md5.js": { ++ "version": "1.3.5", ++ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", ++ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "hash-base": "^3.0.0", ++ "inherits": "^2.0.1", ++ "safe-buffer": "^5.1.2" ++ } ++ }, ++ "node_modules/minimalistic-assert": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", ++ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/minimalistic-crypto-utils": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", ++ "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/minimatch": { ++ "version": "10.2.6", ++ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", ++ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", ++ "dev": true, ++ "license": "BlueOak-1.0.0", ++ "dependencies": { ++ "brace-expansion": "^5.0.8" ++ }, ++ "engines": { ++ "node": "18 || 20 || >=22" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/isaacs" ++ } ++ }, ++ "node_modules/minimist": { ++ "version": "1.2.8", ++ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", ++ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", ++ "dev": true, ++ "license": "MIT", ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/ms": { ++ "version": "2.1.3", ++ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", ++ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/node-addon-api": { ++ "version": "2.0.2", ++ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", ++ "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/node-gyp-build": { ++ "version": "4.8.4", ++ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", ++ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", ++ "dev": true, ++ "license": "MIT", ++ "bin": { ++ "node-gyp-build": "bin.js", ++ "node-gyp-build-optional": "optional.js", ++ "node-gyp-build-test": "build-test.js" ++ } ++ }, ++ "node_modules/nofilter": { ++ "version": "3.1.0", ++ "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", ++ "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=12.19" ++ } ++ }, ++ "node_modules/pbkdf2": { ++ "version": "3.1.6", ++ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.6.tgz", ++ "integrity": "sha512-BT6eelPB1EyGHo8pC0o9Bl6k6SYVhKO1jEbd3lcTrtr7XHdjP8BW1YpfCV3G9Kwkxgattk+S5q2/RvuttCsS1g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "create-hash": "^1.2.0", ++ "create-hmac": "^1.1.7", ++ "ripemd160": "^2.0.3", ++ "safe-buffer": "^5.2.1", ++ "sha.js": "^2.4.12", ++ "to-buffer": "^1.2.2" ++ }, ++ "engines": { ++ "node": ">= 0.10" ++ } ++ }, ++ "node_modules/possible-typed-array-names": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", ++ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/process-nextick-args": { ++ "version": "2.0.1", ++ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", ++ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/proper-lockfile": { ++ "version": "4.1.2", ++ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", ++ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "graceful-fs": "^4.2.4", ++ "retry": "^0.12.0", ++ "signal-exit": "^3.0.2" ++ } ++ }, ++ "node_modules/randombytes": { ++ "version": "2.1.0", ++ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", ++ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "safe-buffer": "^5.1.0" ++ } ++ }, ++ "node_modules/readable-stream": { ++ "version": "3.6.2", ++ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", ++ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "inherits": "^2.0.3", ++ "string_decoder": "^1.1.1", ++ "util-deprecate": "^1.0.1" ++ }, ++ "engines": { ++ "node": ">= 6" ++ } ++ }, ++ "node_modules/retry": { ++ "version": "0.12.0", ++ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", ++ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 4" ++ } ++ }, ++ "node_modules/ripemd160": { ++ "version": "2.0.3", ++ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", ++ "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "hash-base": "^3.1.2", ++ "inherits": "^2.0.4" ++ }, ++ "engines": { ++ "node": ">= 0.8" ++ } ++ }, ++ "node_modules/rlp": { ++ "version": "2.2.7", ++ "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", ++ "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", ++ "dev": true, ++ "license": "MPL-2.0", ++ "dependencies": { ++ "bn.js": "^5.2.0" ++ }, ++ "bin": { ++ "rlp": "bin/rlp" ++ } ++ }, ++ "node_modules/safe-buffer": { ++ "version": "5.2.1", ++ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", ++ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/feross" ++ }, ++ { ++ "type": "patreon", ++ "url": "https://www.patreon.com/feross" ++ }, ++ { ++ "type": "consulting", ++ "url": "https://feross.org/support" ++ } ++ ], ++ "license": "MIT" ++ }, ++ "node_modules/scrypt-js": { ++ "version": "3.0.1", ++ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", ++ "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/secp256k1": { ++ "version": "4.0.5", ++ "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.5.tgz", ++ "integrity": "sha512-SQZi5+/uiJIFPYbeRrVuu77Sr3bFOTq0oCQs67CqYwdmg0lhnqi/8djSWhzNO3GKGOqxBYCdx8zJJv0zUwDDvw==", ++ "dev": true, ++ "hasInstallScript": true, ++ "license": "MIT", ++ "dependencies": { ++ "elliptic": "^6.5.7", ++ "node-addon-api": "^5.0.0", ++ "node-gyp-build": "^4.2.0" ++ }, ++ "engines": { ++ "node": ">=18.0.0" ++ } ++ }, ++ "node_modules/secp256k1/node_modules/node-addon-api": { ++ "version": "5.1.0", ++ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", ++ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/set-function-length": { ++ "version": "1.2.2", ++ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", ++ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "define-data-property": "^1.1.4", ++ "es-errors": "^1.3.0", ++ "function-bind": "^1.1.2", ++ "get-intrinsic": "^1.2.4", ++ "gopd": "^1.0.1", ++ "has-property-descriptors": "^1.0.2" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/setimmediate": { ++ "version": "1.0.5", ++ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", ++ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/sha.js": { ++ "version": "2.4.12", ++ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", ++ "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", ++ "dev": true, ++ "license": "(MIT AND BSD-3-Clause)", ++ "dependencies": { ++ "inherits": "^2.0.4", ++ "safe-buffer": "^5.2.1", ++ "to-buffer": "^1.2.0" ++ }, ++ "bin": { ++ "sha.js": "bin.js" ++ }, ++ "engines": { ++ "node": ">= 0.10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ }, ++ "node_modules/signal-exit": { ++ "version": "3.0.7", ++ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", ++ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/solidity-ast": { ++ "version": "0.4.62", ++ "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.62.tgz", ++ "integrity": "sha512-jSC7msQCkJXIzM8LlDjRZ5cif5w40g6THlXHFk3zchbL5dm3YLoBETvqPGo5KndYkftjhcs5kz1fnTu4d34lVQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/string_decoder": { ++ "version": "1.3.0", ++ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", ++ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "safe-buffer": "~5.2.0" ++ } ++ }, ++ "node_modules/supports-color": { ++ "version": "7.2.0", ++ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", ++ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "has-flag": "^4.0.0" ++ }, ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/to-buffer": { ++ "version": "1.2.2", ++ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", ++ "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "isarray": "^2.0.5", ++ "safe-buffer": "^5.2.1", ++ "typed-array-buffer": "^1.0.3" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/typed-array-buffer": { ++ "version": "1.0.3", ++ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", ++ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "call-bound": "^1.0.3", ++ "es-errors": "^1.3.0", ++ "is-typed-array": "^1.1.14" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ } ++ }, ++ "node_modules/undici-types": { ++ "version": "8.3.0", ++ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", ++ "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/util-deprecate": { ++ "version": "1.0.2", ++ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", ++ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/which-typed-array": { ++ "version": "1.1.22", ++ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", ++ "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", ++ "dev": true, ++ "license": "MIT", ++ "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" ++ }, ++ "engines": { ++ "node": ">= 0.4" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/ljharb" ++ } ++ } ++ } ++} +diff --git a/package.json b/package.json +new file mode 100644 +index 0000000..1612ecb +--- /dev/null ++++ b/package.json +@@ -0,0 +1,14 @@ ++{ ++ "name": "uups-bank-demo", ++ "private": true, ++ "version": "0.1.0", ++ "type": "module", ++ "packageManager": "npm@11.17.0", ++ "engines": { ++ "node": ">=24.18.0 <25", ++ "npm": ">=11.17.0 <12" ++ }, ++ "devDependencies": { ++ "@openzeppelin/upgrades-core": "1.46.0" ++ } ++} +diff --git a/remappings.txt b/remappings.txt +new file mode 100644 +index 0000000..e701804 +--- /dev/null ++++ b/remappings.txt +@@ -0,0 +1,4 @@ ++forge-std/=lib/forge-std/src/ ++openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ ++@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ ++@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +diff --git a/tools/check-upgrades-cli.mjs b/tools/check-upgrades-cli.mjs +new file mode 100644 +index 0000000..61d5706 +--- /dev/null ++++ b/tools/check-upgrades-cli.mjs +@@ -0,0 +1,28 @@ ++import { readFile } from "node:fs/promises"; ++import { createRequire } from "node:module"; ++ ++const require = createRequire(import.meta.url); ++const expectedVersion = "1.46.0"; ++const pluginSource = await readFile( ++ new URL("../lib/openzeppelin-foundry-upgrades/src/internal/Versions.sol", import.meta.url), ++ "utf8" ++); ++const lockfile = JSON.parse( ++ await readFile(new URL("../package-lock.json", import.meta.url), "utf8") ++); ++const lockedVersion = lockfile.packages?.["node_modules/@openzeppelin/upgrades-core"]?.version; ++const installedVersion = require("@openzeppelin/upgrades-core/package.json").version; ++ ++if (!pluginSource.includes('UPGRADES_CORE = "^1.45.0"')) { ++ throw new Error('Expected the vendored plugin to declare UPGRADES_CORE = "^1.45.0".'); ++} ++ ++if (lockedVersion !== expectedVersion) { ++ throw new Error(`Expected package-lock.json to resolve @openzeppelin/upgrades-core to ${expectedVersion}, found ${lockedVersion}.`); ++} ++ ++if (installedVersion !== expectedVersion) { ++ throw new Error(`Expected installed @openzeppelin/upgrades-core to report ${expectedVersion}, found ${installedVersion}.`); ++} ++ ++console.log(`@openzeppelin/upgrades-core ${expectedVersion} is pinned and installed.`); +diff --git a/tools/doctor.sh b/tools/doctor.sh +new file mode 100755 +index 0000000..721b7f2 +--- /dev/null ++++ b/tools/doctor.sh +@@ -0,0 +1,13 @@ ++#!/usr/bin/env bash ++set -euo pipefail ++ ++printf '%s\n' 'Expected: Foundry 1.7.1, Node 24.18.0, npm 11.17.0' ++ ++for tool in forge anvil node npm make; do ++ if command -v "$tool" >/dev/null 2>&1; then ++ printf '%s: ' "$tool" ++ "$tool" --version | sed -n '1p' ++ else ++ printf '%s\n' "missing: $tool" ++ fi ++done +diff --git a/web/eslint.config.js b/web/eslint.config.js +new file mode 100644 +index 0000000..c272be2 +--- /dev/null ++++ b/web/eslint.config.js +@@ -0,0 +1,26 @@ ++import js from "@eslint/js"; ++import globals from "globals"; ++import reactHooks from "eslint-plugin-react-hooks"; ++import reactRefresh from "eslint-plugin-react-refresh"; ++import tseslint from "typescript-eslint"; ++ ++export default tseslint.config( ++ { ignores: ["dist", "coverage"] }, ++ js.configs.recommended, ++ ...tseslint.configs.recommended, ++ { ++ files: ["**/*.{ts,tsx}"], ++ languageOptions: { ++ ecmaVersion: 2022, ++ globals: { ...globals.browser, ...globals.node } ++ }, ++ plugins: { ++ "react-hooks": reactHooks, ++ "react-refresh": reactRefresh ++ }, ++ rules: { ++ ...reactHooks.configs.recommended.rules, ++ "react-refresh/only-export-components": ["warn", { allowConstantExport: true }] ++ } ++ } ++); +diff --git a/web/index.html b/web/index.html +new file mode 100644 +index 0000000..d91f1f3 +--- /dev/null ++++ b/web/index.html +@@ -0,0 +1,11 @@ ++ ++ ++ ++ ++ ++ UUPS Bank Operations Console ++ ++ ++
++ ++ +diff --git a/web/package-lock.json b/web/package-lock.json +new file mode 100644 +index 0000000..49d9592 +--- /dev/null ++++ b/web/package-lock.json +@@ -0,0 +1,4531 @@ ++{ ++ "name": "uups-bank-operations-console", ++ "version": "0.1.0", ++ "lockfileVersion": 3, ++ "requires": true, ++ "packages": { ++ "": { ++ "name": "uups-bank-operations-console", ++ "version": "0.1.0", ++ "dependencies": { ++ "@tanstack/react-query": "5.101.4", ++ "react": "19.2.8", ++ "react-dom": "19.2.8", ++ "viem": "2.55.8", ++ "wagmi": "3.7.5" ++ }, ++ "devDependencies": { ++ "@eslint/js": "10.0.1", ++ "@testing-library/dom": "10.4.1", ++ "@testing-library/react": "16.3.2", ++ "@types/node": "24.10.0", ++ "@types/react": "19.2.14", ++ "@types/react-dom": "19.2.4", ++ "@typescript/native": "npm:typescript@7.0.2", ++ "@vitejs/plugin-react": "6.0.4", ++ "eslint": "10.0.1", ++ "eslint-plugin-react-hooks": "7.1.1", ++ "eslint-plugin-react-refresh": "0.5.3", ++ "globals": "17.7.0", ++ "jsdom": "30.0.1", ++ "typescript": "npm:@typescript/typescript6@6.0.2", ++ "typescript-eslint": "8.65.0", ++ "vite": "8.2.0", ++ "vitest": "4.1.10" ++ }, ++ "engines": { ++ "node": ">=24.18.0 <25", ++ "npm": ">=11.17.0 <12" ++ } ++ }, ++ "node_modules/@adraffy/ens-normalize": { ++ "version": "1.11.1", ++ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", ++ "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", ++ "license": "MIT" ++ }, ++ "node_modules/@asamuzakjp/css-color": { ++ "version": "6.0.7", ++ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", ++ "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@csstools/css-calc": "^3.3.0", ++ "@csstools/css-color-parser": "^4.1.10", ++ "@csstools/css-parser-algorithms": "^4.0.0", ++ "@csstools/css-tokenizer": "^4.0.0", ++ "lru-cache": "^11.5.2" ++ }, ++ "engines": { ++ "node": "^22.13.0 || >=24.0.0" ++ } ++ }, ++ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { ++ "version": "11.5.2", ++ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", ++ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", ++ "dev": true, ++ "license": "BlueOak-1.0.0", ++ "engines": { ++ "node": "20 || >=22" ++ } ++ }, ++ "node_modules/@asamuzakjp/dom-selector": { ++ "version": "8.3.2", ++ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", ++ "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "bidi-js": "^1.0.3", ++ "css-tree": "^3.2.1", ++ "is-potential-custom-element-name": "^1.0.1", ++ "lru-cache": "^11.5.2" ++ }, ++ "engines": { ++ "node": "^22.13.0 || >=24.0.0" ++ } ++ }, ++ "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { ++ "version": "11.5.2", ++ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", ++ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", ++ "dev": true, ++ "license": "BlueOak-1.0.0", ++ "engines": { ++ "node": "20 || >=22" ++ } ++ }, ++ "node_modules/@babel/code-frame": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", ++ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/helper-validator-identifier": "^7.29.7", ++ "js-tokens": "^4.0.0", ++ "picocolors": "^1.1.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/compat-data": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", ++ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/core": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", ++ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/code-frame": "^7.29.7", ++ "@babel/generator": "^7.29.7", ++ "@babel/helper-compilation-targets": "^7.29.7", ++ "@babel/helper-module-transforms": "^7.29.7", ++ "@babel/helpers": "^7.29.7", ++ "@babel/parser": "^7.29.7", ++ "@babel/template": "^7.29.7", ++ "@babel/traverse": "^7.29.7", ++ "@babel/types": "^7.29.7", ++ "@jridgewell/remapping": "^2.3.5", ++ "convert-source-map": "^2.0.0", ++ "debug": "^4.1.0", ++ "gensync": "^1.0.0-beta.2", ++ "json5": "^2.2.3", ++ "semver": "^6.3.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/babel" ++ } ++ }, ++ "node_modules/@babel/generator": { ++ "version": "7.29.8", ++ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", ++ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/parser": "^7.29.8", ++ "@babel/types": "^7.29.8", ++ "@jridgewell/gen-mapping": "^0.3.12", ++ "@jridgewell/trace-mapping": "^0.3.28", ++ "jsesc": "^3.0.2" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-compilation-targets": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", ++ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/compat-data": "^7.29.7", ++ "@babel/helper-validator-option": "^7.29.7", ++ "browserslist": "^4.24.0", ++ "lru-cache": "^5.1.1", ++ "semver": "^6.3.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-globals": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", ++ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-module-imports": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", ++ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/traverse": "^7.29.7", ++ "@babel/types": "^7.29.7" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-module-transforms": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", ++ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/helper-module-imports": "^7.29.7", ++ "@babel/helper-validator-identifier": "^7.29.7", ++ "@babel/traverse": "^7.29.7" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ }, ++ "peerDependencies": { ++ "@babel/core": "^7.0.0" ++ } ++ }, ++ "node_modules/@babel/helper-string-parser": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", ++ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-validator-identifier": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", ++ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helper-validator-option": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", ++ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/helpers": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", ++ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/template": "^7.29.7", ++ "@babel/types": "^7.29.7" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/parser": { ++ "version": "7.29.8", ++ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", ++ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/types": "^7.29.8" ++ }, ++ "bin": { ++ "parser": "bin/babel-parser.js" ++ }, ++ "engines": { ++ "node": ">=6.0.0" ++ } ++ }, ++ "node_modules/@babel/runtime": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", ++ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/template": { ++ "version": "7.29.7", ++ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", ++ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/code-frame": "^7.29.7", ++ "@babel/parser": "^7.29.7", ++ "@babel/types": "^7.29.7" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/traverse": { ++ "version": "7.29.8", ++ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", ++ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/code-frame": "^7.29.7", ++ "@babel/generator": "^7.29.8", ++ "@babel/helper-globals": "^7.29.7", ++ "@babel/parser": "^7.29.8", ++ "@babel/template": "^7.29.7", ++ "@babel/types": "^7.29.8", ++ "debug": "^4.3.1" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@babel/types": { ++ "version": "7.29.8", ++ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", ++ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/helper-string-parser": "^7.29.7", ++ "@babel/helper-validator-identifier": "^7.29.7" ++ }, ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/@bramus/specificity": { ++ "version": "2.4.2", ++ "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", ++ "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "css-tree": "^3.0.0" ++ }, ++ "bin": { ++ "specificity": "bin/cli.js" ++ } ++ }, ++ "node_modules/@csstools/color-helpers": { ++ "version": "6.1.1", ++ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", ++ "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/csstools" ++ }, ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/csstools" ++ } ++ ], ++ "license": "MIT-0", ++ "engines": { ++ "node": ">=20.19.0" ++ } ++ }, ++ "node_modules/@csstools/css-calc": { ++ "version": "3.3.0", ++ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", ++ "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/csstools" ++ }, ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/csstools" ++ } ++ ], ++ "license": "MIT", ++ "engines": { ++ "node": ">=20.19.0" ++ }, ++ "peerDependencies": { ++ "@csstools/css-parser-algorithms": "^4.0.0", ++ "@csstools/css-tokenizer": "^4.0.0" ++ } ++ }, ++ "node_modules/@csstools/css-color-parser": { ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.0.tgz", ++ "integrity": "sha512-5+5LEmFuY1AjXdYhmgjTJogtQnP1evJ1zrBZGUNZ0thkpwnnmKxcHdAMn/OtFjAb25zA+jKDVYVRl+5G7rjv1A==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/csstools" ++ }, ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/csstools" ++ } ++ ], ++ "license": "MIT", ++ "dependencies": { ++ "@csstools/color-helpers": "^6.1.1", ++ "@csstools/css-calc": "^3.3.0" ++ }, ++ "engines": { ++ "node": ">=20.19.0" ++ }, ++ "peerDependencies": { ++ "@csstools/css-parser-algorithms": "^4.0.0", ++ "@csstools/css-tokenizer": "^4.0.0" ++ } ++ }, ++ "node_modules/@csstools/css-parser-algorithms": { ++ "version": "4.0.0", ++ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", ++ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/csstools" ++ }, ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/csstools" ++ } ++ ], ++ "license": "MIT", ++ "engines": { ++ "node": ">=20.19.0" ++ }, ++ "peerDependencies": { ++ "@csstools/css-tokenizer": "^4.0.0" ++ } ++ }, ++ "node_modules/@csstools/css-syntax-patches-for-csstree": { ++ "version": "1.1.8", ++ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.8.tgz", ++ "integrity": "sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/csstools" ++ }, ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/csstools" ++ } ++ ], ++ "license": "MIT-0", ++ "peerDependencies": { ++ "css-tree": "^3.2.1" ++ }, ++ "peerDependenciesMeta": { ++ "css-tree": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@csstools/css-tokenizer": { ++ "version": "4.0.0", ++ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", ++ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/csstools" ++ }, ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/csstools" ++ } ++ ], ++ "license": "MIT", ++ "engines": { ++ "node": ">=20.19.0" ++ } ++ }, ++ "node_modules/@eslint-community/eslint-utils": { ++ "version": "4.10.1", ++ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", ++ "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "eslint-visitor-keys": "^3.4.3" ++ }, ++ "engines": { ++ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/eslint" ++ }, ++ "peerDependencies": { ++ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" ++ } ++ }, ++ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { ++ "version": "3.4.3", ++ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", ++ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/eslint" ++ } ++ }, ++ "node_modules/@eslint-community/regexpp": { ++ "version": "4.12.2", ++ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", ++ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" ++ } ++ }, ++ "node_modules/@eslint/config-array": { ++ "version": "0.23.5", ++ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", ++ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@eslint/object-schema": "^3.0.5", ++ "debug": "^4.3.1", ++ "minimatch": "^10.2.4" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ } ++ }, ++ "node_modules/@eslint/config-helpers": { ++ "version": "0.5.5", ++ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", ++ "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@eslint/core": "^1.2.1" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ } ++ }, ++ "node_modules/@eslint/core": { ++ "version": "1.2.1", ++ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", ++ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@types/json-schema": "^7.0.15" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ } ++ }, ++ "node_modules/@eslint/js": { ++ "version": "10.0.1", ++ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", ++ "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ }, ++ "funding": { ++ "url": "https://eslint.org/donate" ++ }, ++ "peerDependencies": { ++ "eslint": "^10.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "eslint": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@eslint/object-schema": { ++ "version": "3.0.5", ++ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", ++ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ } ++ }, ++ "node_modules/@eslint/plugin-kit": { ++ "version": "0.6.1", ++ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", ++ "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@eslint/core": "^1.1.1", ++ "levn": "^0.4.1" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ } ++ }, ++ "node_modules/@exodus/bytes": { ++ "version": "1.15.1", ++ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", ++ "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" ++ }, ++ "peerDependencies": { ++ "@noble/hashes": "^1.8.0 || ^2.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "@noble/hashes": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@humanfs/core": { ++ "version": "0.19.2", ++ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", ++ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@humanfs/types": "^0.15.0" ++ }, ++ "engines": { ++ "node": ">=18.18.0" ++ } ++ }, ++ "node_modules/@humanfs/node": { ++ "version": "0.16.8", ++ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", ++ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@humanfs/core": "^0.19.2", ++ "@humanfs/types": "^0.15.0", ++ "@humanwhocodes/retry": "^0.4.0" ++ }, ++ "engines": { ++ "node": ">=18.18.0" ++ } ++ }, ++ "node_modules/@humanfs/types": { ++ "version": "0.15.0", ++ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", ++ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": ">=18.18.0" ++ } ++ }, ++ "node_modules/@humanwhocodes/module-importer": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", ++ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": ">=12.22" ++ }, ++ "funding": { ++ "type": "github", ++ "url": "https://github.com/sponsors/nzakas" ++ } ++ }, ++ "node_modules/@humanwhocodes/retry": { ++ "version": "0.4.3", ++ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", ++ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": ">=18.18" ++ }, ++ "funding": { ++ "type": "github", ++ "url": "https://github.com/sponsors/nzakas" ++ } ++ }, ++ "node_modules/@jridgewell/gen-mapping": { ++ "version": "0.3.13", ++ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", ++ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@jridgewell/sourcemap-codec": "^1.5.0", ++ "@jridgewell/trace-mapping": "^0.3.24" ++ } ++ }, ++ "node_modules/@jridgewell/remapping": { ++ "version": "2.3.5", ++ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", ++ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@jridgewell/gen-mapping": "^0.3.5", ++ "@jridgewell/trace-mapping": "^0.3.24" ++ } ++ }, ++ "node_modules/@jridgewell/resolve-uri": { ++ "version": "3.1.2", ++ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", ++ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.0.0" ++ } ++ }, ++ "node_modules/@jridgewell/sourcemap-codec": { ++ "version": "1.5.5", ++ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", ++ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@jridgewell/trace-mapping": { ++ "version": "0.3.31", ++ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", ++ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@jridgewell/resolve-uri": "^3.1.0", ++ "@jridgewell/sourcemap-codec": "^1.4.14" ++ } ++ }, ++ "node_modules/@noble/ciphers": { ++ "version": "1.3.0", ++ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", ++ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", ++ "license": "MIT", ++ "engines": { ++ "node": "^14.21.3 || >=16" ++ }, ++ "funding": { ++ "url": "https://paulmillr.com/funding/" ++ } ++ }, ++ "node_modules/@noble/curves": { ++ "version": "1.9.1", ++ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", ++ "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", ++ "license": "MIT", ++ "dependencies": { ++ "@noble/hashes": "1.8.0" ++ }, ++ "engines": { ++ "node": "^14.21.3 || >=16" ++ }, ++ "funding": { ++ "url": "https://paulmillr.com/funding/" ++ } ++ }, ++ "node_modules/@noble/hashes": { ++ "version": "1.8.0", ++ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", ++ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", ++ "license": "MIT", ++ "engines": { ++ "node": "^14.21.3 || >=16" ++ }, ++ "funding": { ++ "url": "https://paulmillr.com/funding/" ++ } ++ }, ++ "node_modules/@oxc-project/types": { ++ "version": "0.144.0", ++ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", ++ "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", ++ "dev": true, ++ "license": "MIT", ++ "funding": { ++ "url": "https://github.com/sponsors/Boshen" ++ } ++ }, ++ "node_modules/@rolldown/binding-android-arm64": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", ++ "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "android" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-darwin-arm64": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", ++ "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "darwin" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-darwin-x64": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", ++ "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "darwin" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-freebsd-x64": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", ++ "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "freebsd" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-linux-arm-gnueabihf": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", ++ "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", ++ "cpu": [ ++ "arm" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-linux-arm64-gnu": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", ++ "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "libc": [ ++ "glibc" ++ ], ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-linux-arm64-musl": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", ++ "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "libc": [ ++ "musl" ++ ], ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-linux-ppc64-gnu": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", ++ "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", ++ "cpu": [ ++ "ppc64" ++ ], ++ "dev": true, ++ "libc": [ ++ "glibc" ++ ], ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-linux-s390x-gnu": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", ++ "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", ++ "cpu": [ ++ "s390x" ++ ], ++ "dev": true, ++ "libc": [ ++ "glibc" ++ ], ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-linux-x64-gnu": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", ++ "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "libc": [ ++ "glibc" ++ ], ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-linux-x64-musl": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", ++ "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "libc": [ ++ "musl" ++ ], ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-openharmony-arm64": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", ++ "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "openharmony" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-win32-arm64-msvc": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", ++ "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "win32" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/binding-win32-x64-msvc": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", ++ "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "win32" ++ ], ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ } ++ }, ++ "node_modules/@rolldown/pluginutils": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", ++ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@scure/base": { ++ "version": "1.2.6", ++ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", ++ "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", ++ "license": "MIT", ++ "funding": { ++ "url": "https://paulmillr.com/funding/" ++ } ++ }, ++ "node_modules/@scure/bip32": { ++ "version": "1.7.0", ++ "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", ++ "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", ++ "license": "MIT", ++ "dependencies": { ++ "@noble/curves": "~1.9.0", ++ "@noble/hashes": "~1.8.0", ++ "@scure/base": "~1.2.5" ++ }, ++ "funding": { ++ "url": "https://paulmillr.com/funding/" ++ } ++ }, ++ "node_modules/@scure/bip39": { ++ "version": "1.6.0", ++ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", ++ "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", ++ "license": "MIT", ++ "dependencies": { ++ "@noble/hashes": "~1.8.0", ++ "@scure/base": "~1.2.5" ++ }, ++ "funding": { ++ "url": "https://paulmillr.com/funding/" ++ } ++ }, ++ "node_modules/@standard-schema/spec": { ++ "version": "1.1.0", ++ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", ++ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@tanstack/query-core": { ++ "version": "5.101.4", ++ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", ++ "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", ++ "license": "MIT", ++ "funding": { ++ "type": "github", ++ "url": "https://github.com/sponsors/tannerlinsley" ++ } ++ }, ++ "node_modules/@tanstack/react-query": { ++ "version": "5.101.4", ++ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", ++ "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", ++ "license": "MIT", ++ "dependencies": { ++ "@tanstack/query-core": "5.101.4" ++ }, ++ "funding": { ++ "type": "github", ++ "url": "https://github.com/sponsors/tannerlinsley" ++ }, ++ "peerDependencies": { ++ "react": "^18 || ^19" ++ } ++ }, ++ "node_modules/@testing-library/dom": { ++ "version": "10.4.1", ++ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", ++ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/code-frame": "^7.10.4", ++ "@babel/runtime": "^7.12.5", ++ "@types/aria-query": "^5.0.1", ++ "aria-query": "5.3.0", ++ "dom-accessibility-api": "^0.5.9", ++ "lz-string": "^1.5.0", ++ "picocolors": "1.1.1", ++ "pretty-format": "^27.0.2" ++ }, ++ "engines": { ++ "node": ">=18" ++ } ++ }, ++ "node_modules/@testing-library/react": { ++ "version": "16.3.2", ++ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", ++ "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/runtime": "^7.12.5" ++ }, ++ "engines": { ++ "node": ">=18" ++ }, ++ "peerDependencies": { ++ "@testing-library/dom": "^10.0.0", ++ "@types/react": "^18.0.0 || ^19.0.0", ++ "@types/react-dom": "^18.0.0 || ^19.0.0", ++ "react": "^18.0.0 || ^19.0.0", ++ "react-dom": "^18.0.0 || ^19.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "@types/react": { ++ "optional": true ++ }, ++ "@types/react-dom": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@types/aria-query": { ++ "version": "5.0.4", ++ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", ++ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@types/chai": { ++ "version": "5.2.3", ++ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", ++ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@types/deep-eql": "*", ++ "assertion-error": "^2.0.1" ++ } ++ }, ++ "node_modules/@types/deep-eql": { ++ "version": "4.0.2", ++ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", ++ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@types/esrecurse": { ++ "version": "4.3.1", ++ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", ++ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@types/estree": { ++ "version": "1.0.9", ++ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", ++ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@types/json-schema": { ++ "version": "7.0.15", ++ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", ++ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/@types/node": { ++ "version": "24.10.0", ++ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", ++ "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "undici-types": "~7.16.0" ++ } ++ }, ++ "node_modules/@types/react": { ++ "version": "19.2.14", ++ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", ++ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", ++ "devOptional": true, ++ "license": "MIT", ++ "dependencies": { ++ "csstype": "^3.2.2" ++ } ++ }, ++ "node_modules/@types/react-dom": { ++ "version": "19.2.4", ++ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", ++ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", ++ "dev": true, ++ "license": "MIT", ++ "peerDependencies": { ++ "@types/react": "^19.2.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/eslint-plugin": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", ++ "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@eslint-community/regexpp": "^4.12.2", ++ "@typescript-eslint/scope-manager": "8.65.0", ++ "@typescript-eslint/type-utils": "8.65.0", ++ "@typescript-eslint/utils": "8.65.0", ++ "@typescript-eslint/visitor-keys": "8.65.0", ++ "ignore": "^7.0.5", ++ "natural-compare": "^1.4.0", ++ "ts-api-utils": "^2.5.0" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "@typescript-eslint/parser": "^8.65.0", ++ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { ++ "version": "7.0.6", ++ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", ++ "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 4" ++ } ++ }, ++ "node_modules/@typescript-eslint/parser": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", ++ "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@typescript-eslint/scope-manager": "8.65.0", ++ "@typescript-eslint/types": "8.65.0", ++ "@typescript-eslint/typescript-estree": "8.65.0", ++ "@typescript-eslint/visitor-keys": "8.65.0", ++ "debug": "^4.4.3" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/project-service": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", ++ "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@typescript-eslint/tsconfig-utils": "^8.65.0", ++ "@typescript-eslint/types": "^8.65.0", ++ "debug": "^4.4.3" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/scope-manager": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", ++ "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@typescript-eslint/types": "8.65.0", ++ "@typescript-eslint/visitor-keys": "8.65.0" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ } ++ }, ++ "node_modules/@typescript-eslint/tsconfig-utils": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", ++ "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/type-utils": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", ++ "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@typescript-eslint/types": "8.65.0", ++ "@typescript-eslint/typescript-estree": "8.65.0", ++ "@typescript-eslint/utils": "8.65.0", ++ "debug": "^4.4.3", ++ "ts-api-utils": "^2.5.0" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/types": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", ++ "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ } ++ }, ++ "node_modules/@typescript-eslint/typescript-estree": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", ++ "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@typescript-eslint/project-service": "8.65.0", ++ "@typescript-eslint/tsconfig-utils": "8.65.0", ++ "@typescript-eslint/types": "8.65.0", ++ "@typescript-eslint/visitor-keys": "8.65.0", ++ "debug": "^4.4.3", ++ "minimatch": "^10.2.2", ++ "semver": "^7.7.3", ++ "tinyglobby": "^0.2.15", ++ "ts-api-utils": "^2.5.0" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { ++ "version": "7.8.5", ++ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", ++ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", ++ "dev": true, ++ "license": "ISC", ++ "bin": { ++ "semver": "bin/semver.js" ++ }, ++ "engines": { ++ "node": ">=10" ++ } ++ }, ++ "node_modules/@typescript-eslint/utils": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", ++ "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@eslint-community/eslint-utils": "^4.9.1", ++ "@typescript-eslint/scope-manager": "8.65.0", ++ "@typescript-eslint/types": "8.65.0", ++ "@typescript-eslint/typescript-estree": "8.65.0" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/@typescript-eslint/visitor-keys": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", ++ "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@typescript-eslint/types": "8.65.0", ++ "eslint-visitor-keys": "^5.0.0" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ } ++ }, ++ "node_modules/@typescript/native": { ++ "name": "typescript", ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", ++ "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "bin": { ++ "tsc": "bin/tsc" ++ }, ++ "engines": { ++ "node": ">=16.20.0" ++ }, ++ "optionalDependencies": { ++ "@typescript/typescript-aix-ppc64": "7.0.2", ++ "@typescript/typescript-darwin-arm64": "7.0.2", ++ "@typescript/typescript-darwin-x64": "7.0.2", ++ "@typescript/typescript-freebsd-arm64": "7.0.2", ++ "@typescript/typescript-freebsd-x64": "7.0.2", ++ "@typescript/typescript-linux-arm": "7.0.2", ++ "@typescript/typescript-linux-arm64": "7.0.2", ++ "@typescript/typescript-linux-loong64": "7.0.2", ++ "@typescript/typescript-linux-mips64el": "7.0.2", ++ "@typescript/typescript-linux-ppc64": "7.0.2", ++ "@typescript/typescript-linux-riscv64": "7.0.2", ++ "@typescript/typescript-linux-s390x": "7.0.2", ++ "@typescript/typescript-linux-x64": "7.0.2", ++ "@typescript/typescript-netbsd-arm64": "7.0.2", ++ "@typescript/typescript-netbsd-x64": "7.0.2", ++ "@typescript/typescript-openbsd-arm64": "7.0.2", ++ "@typescript/typescript-openbsd-x64": "7.0.2", ++ "@typescript/typescript-sunos-x64": "7.0.2", ++ "@typescript/typescript-win32-arm64": "7.0.2", ++ "@typescript/typescript-win32-x64": "7.0.2" ++ } ++ }, ++ "node_modules/@typescript/old": { ++ "name": "typescript", ++ "version": "6.0.3", ++ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", ++ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", ++ "devOptional": true, ++ "license": "Apache-2.0", ++ "bin": { ++ "tsc": "bin/tsc", ++ "tsserver": "bin/tsserver" ++ }, ++ "engines": { ++ "node": ">=14.17" ++ } ++ }, ++ "node_modules/@typescript/typescript-aix-ppc64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", ++ "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", ++ "cpu": [ ++ "ppc64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "aix" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-darwin-arm64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", ++ "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "darwin" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-darwin-x64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", ++ "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "darwin" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-freebsd-arm64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", ++ "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "freebsd" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-freebsd-x64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", ++ "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "freebsd" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-arm": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", ++ "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", ++ "cpu": [ ++ "arm" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-arm64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", ++ "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-loong64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", ++ "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", ++ "cpu": [ ++ "loong64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-mips64el": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", ++ "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", ++ "cpu": [ ++ "mips64el" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-ppc64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", ++ "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", ++ "cpu": [ ++ "ppc64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-riscv64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", ++ "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", ++ "cpu": [ ++ "riscv64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-s390x": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", ++ "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", ++ "cpu": [ ++ "s390x" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-linux-x64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", ++ "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-netbsd-arm64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", ++ "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "netbsd" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-netbsd-x64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", ++ "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "netbsd" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-openbsd-arm64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", ++ "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "openbsd" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-openbsd-x64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", ++ "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "openbsd" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-sunos-x64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", ++ "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "sunos" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-win32-arm64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", ++ "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "win32" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@typescript/typescript-win32-x64": { ++ "version": "7.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", ++ "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "Apache-2.0", ++ "optional": true, ++ "os": [ ++ "win32" ++ ], ++ "engines": { ++ "node": ">=16.20.0" ++ } ++ }, ++ "node_modules/@vitejs/plugin-react": { ++ "version": "6.0.4", ++ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.4.tgz", ++ "integrity": "sha512-XcCQz0TBpBgljhj0gMuuDj49i6Ytqh5q1osT/Gp5uAVJUCTWxyskk/l1jwYYiu2xcNHHipdMz40EGfM1VdamVg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@rolldown/pluginutils": "^1.0.1" ++ }, ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ }, ++ "peerDependencies": { ++ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", ++ "babel-plugin-react-compiler": "^1.0.0", ++ "vite": "^8.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "@rolldown/plugin-babel": { ++ "optional": true ++ }, ++ "babel-plugin-react-compiler": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@vitest/expect": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", ++ "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", ++ "dev": true, ++ "license": "MIT", ++ "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" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ } ++ }, ++ "node_modules/@vitest/mocker": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", ++ "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@vitest/spy": "4.1.10", ++ "estree-walker": "^3.0.3", ++ "magic-string": "^0.30.21" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ }, ++ "peerDependencies": { ++ "msw": "^2.4.9", ++ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "msw": { ++ "optional": true ++ }, ++ "vite": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@vitest/pretty-format": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", ++ "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "tinyrainbow": "^3.1.0" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ } ++ }, ++ "node_modules/@vitest/runner": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", ++ "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@vitest/utils": "4.1.10", ++ "pathe": "^2.0.3" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ } ++ }, ++ "node_modules/@vitest/snapshot": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", ++ "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@vitest/pretty-format": "4.1.10", ++ "@vitest/utils": "4.1.10", ++ "magic-string": "^0.30.21", ++ "pathe": "^2.0.3" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ } ++ }, ++ "node_modules/@vitest/spy": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", ++ "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", ++ "dev": true, ++ "license": "MIT", ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ } ++ }, ++ "node_modules/@vitest/utils": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", ++ "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@vitest/pretty-format": "4.1.10", ++ "convert-source-map": "^2.0.0", ++ "tinyrainbow": "^3.1.0" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ } ++ }, ++ "node_modules/@wagmi/connectors": { ++ "version": "8.0.26", ++ "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-8.0.26.tgz", ++ "integrity": "sha512-W/mDMvQ6Q1zIehY/ZjGRBjI/GgmXEfcF6z45b6uiSSvjGgtg4dT9eLw/NGi4x47eMKCrIuIqFYFLVDpQawx8GA==", ++ "license": "MIT", ++ "funding": { ++ "url": "https://github.com/sponsors/wevm" ++ }, ++ "peerDependencies": { ++ "@base-org/account": "^2.5.1", ++ "@coinbase/wallet-sdk": "^4.3.6", ++ "@metamask/connect-evm": "^2.1.0", ++ "@safe-global/safe-apps-provider": "~0.18.6", ++ "@safe-global/safe-apps-sdk": "^9.1.0", ++ "@wagmi/core": "3.6.4", ++ "@walletconnect/ethereum-provider": "^2.21.1", ++ "accounts": "~0.14", ++ "porto": "~0.2.35", ++ "typescript": ">=5.9.3", ++ "viem": "2.x" ++ }, ++ "peerDependenciesMeta": { ++ "@base-org/account": { ++ "optional": true ++ }, ++ "@coinbase/wallet-sdk": { ++ "optional": true ++ }, ++ "@metamask/connect-evm": { ++ "optional": true ++ }, ++ "@safe-global/safe-apps-provider": { ++ "optional": true ++ }, ++ "@safe-global/safe-apps-sdk": { ++ "optional": true ++ }, ++ "@walletconnect/ethereum-provider": { ++ "optional": true ++ }, ++ "accounts": { ++ "optional": true ++ }, ++ "porto": { ++ "optional": true ++ }, ++ "typescript": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/@wagmi/core": { ++ "version": "3.6.4", ++ "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-3.6.4.tgz", ++ "integrity": "sha512-YERJ+vbFZw3UI6p4KoFU5DkxkqmtzBq5lddpgVAvcu5Tz7kSicKVA3DMCWvjVAFsikNdZYIs39FIkMGWfzpSMQ==", ++ "license": "MIT", ++ "dependencies": { ++ "eventemitter3": "5.0.1", ++ "mipd": "0.0.7", ++ "zustand": "5.0.0" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/wevm" ++ }, ++ "peerDependencies": { ++ "@tanstack/query-core": ">=5.0.0", ++ "accounts": "~0.14", ++ "typescript": ">=5.9.3", ++ "viem": "2.x" ++ }, ++ "peerDependenciesMeta": { ++ "@tanstack/query-core": { ++ "optional": true ++ }, ++ "accounts": { ++ "optional": true ++ }, ++ "typescript": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/abitype": { ++ "version": "1.2.3", ++ "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", ++ "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", ++ "license": "MIT", ++ "funding": { ++ "url": "https://github.com/sponsors/wevm" ++ }, ++ "peerDependencies": { ++ "typescript": ">=5.0.4", ++ "zod": "^3.22.0 || ^4.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "typescript": { ++ "optional": true ++ }, ++ "zod": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/acorn": { ++ "version": "8.18.0", ++ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", ++ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", ++ "dev": true, ++ "license": "MIT", ++ "bin": { ++ "acorn": "bin/acorn" ++ }, ++ "engines": { ++ "node": ">=0.4.0" ++ } ++ }, ++ "node_modules/acorn-jsx": { ++ "version": "5.3.2", ++ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", ++ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", ++ "dev": true, ++ "license": "MIT", ++ "peerDependencies": { ++ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" ++ } ++ }, ++ "node_modules/ajv": { ++ "version": "6.15.0", ++ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", ++ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "fast-deep-equal": "^3.1.1", ++ "fast-json-stable-stringify": "^2.0.0", ++ "json-schema-traverse": "^0.4.1", ++ "uri-js": "^4.2.2" ++ }, ++ "funding": { ++ "type": "github", ++ "url": "https://github.com/sponsors/epoberezkin" ++ } ++ }, ++ "node_modules/ansi-regex": { ++ "version": "5.0.1", ++ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", ++ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/ansi-styles": { ++ "version": "5.2.0", ++ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", ++ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/chalk/ansi-styles?sponsor=1" ++ } ++ }, ++ "node_modules/aria-query": { ++ "version": "5.3.0", ++ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", ++ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "dequal": "^2.0.3" ++ } ++ }, ++ "node_modules/assertion-error": { ++ "version": "2.0.1", ++ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", ++ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=12" ++ } ++ }, ++ "node_modules/balanced-match": { ++ "version": "4.0.4", ++ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", ++ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": "18 || 20 || >=22" ++ } ++ }, ++ "node_modules/baseline-browser-mapping": { ++ "version": "2.11.15", ++ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz", ++ "integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "bin": { ++ "baseline-browser-mapping": "dist/cli.cjs" ++ }, ++ "engines": { ++ "node": ">=6.0.0" ++ } ++ }, ++ "node_modules/bidi-js": { ++ "version": "1.0.3", ++ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", ++ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "require-from-string": "^2.0.2" ++ } ++ }, ++ "node_modules/brace-expansion": { ++ "version": "5.0.9", ++ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", ++ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "balanced-match": "^4.0.2" ++ }, ++ "engines": { ++ "node": "20 || >=22" ++ } ++ }, ++ "node_modules/browserslist": { ++ "version": "4.28.8", ++ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", ++ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/browserslist" ++ }, ++ { ++ "type": "tidelift", ++ "url": "https://tidelift.com/funding/github/npm/browserslist" ++ }, ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/ai" ++ } ++ ], ++ "license": "MIT", ++ "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" ++ }, ++ "engines": { ++ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" ++ } ++ }, ++ "node_modules/caniuse-lite": { ++ "version": "1.0.30001809", ++ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", ++ "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/browserslist" ++ }, ++ { ++ "type": "tidelift", ++ "url": "https://tidelift.com/funding/github/npm/caniuse-lite" ++ }, ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/ai" ++ } ++ ], ++ "license": "CC-BY-4.0" ++ }, ++ "node_modules/chai": { ++ "version": "6.2.2", ++ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", ++ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=18" ++ } ++ }, ++ "node_modules/convert-source-map": { ++ "version": "2.0.0", ++ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", ++ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/cross-spawn": { ++ "version": "7.0.6", ++ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", ++ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "path-key": "^3.1.0", ++ "shebang-command": "^2.0.0", ++ "which": "^2.0.1" ++ }, ++ "engines": { ++ "node": ">= 8" ++ } ++ }, ++ "node_modules/css-tree": { ++ "version": "3.2.1", ++ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", ++ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "mdn-data": "2.27.1", ++ "source-map-js": "^1.2.1" ++ }, ++ "engines": { ++ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" ++ } ++ }, ++ "node_modules/csstype": { ++ "version": "3.2.3", ++ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", ++ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", ++ "devOptional": true, ++ "license": "MIT" ++ }, ++ "node_modules/data-urls": { ++ "version": "7.0.0", ++ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", ++ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "whatwg-mimetype": "^5.0.0", ++ "whatwg-url": "^16.0.0" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" ++ } ++ }, ++ "node_modules/data-urls/node_modules/whatwg-url": { ++ "version": "16.0.1", ++ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", ++ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@exodus/bytes": "^1.11.0", ++ "tr46": "^6.0.0", ++ "webidl-conversions": "^8.0.1" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" ++ } ++ }, ++ "node_modules/debug": { ++ "version": "4.4.3", ++ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", ++ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "ms": "^2.1.3" ++ }, ++ "engines": { ++ "node": ">=6.0" ++ }, ++ "peerDependenciesMeta": { ++ "supports-color": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/decimal.js": { ++ "version": "10.6.0", ++ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", ++ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/deep-is": { ++ "version": "0.1.4", ++ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", ++ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/dequal": { ++ "version": "2.0.3", ++ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", ++ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6" ++ } ++ }, ++ "node_modules/detect-libc": { ++ "version": "2.1.2", ++ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", ++ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/dom-accessibility-api": { ++ "version": "0.5.16", ++ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", ++ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/electron-to-chromium": { ++ "version": "1.5.408", ++ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.408.tgz", ++ "integrity": "sha512-SLoprcYpJ/OH2v2ps0+N5biv9H4/KBT3+YmmDew64TwK5y9j2wv7pMOFY7IorVkyMtEyLSCRlXKLsNlakeAlPw==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/entities": { ++ "version": "8.0.0", ++ "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", ++ "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "engines": { ++ "node": ">=20.19.0" ++ }, ++ "funding": { ++ "url": "https://github.com/fb55/entities?sponsor=1" ++ } ++ }, ++ "node_modules/es-module-lexer": { ++ "version": "2.3.2", ++ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", ++ "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/escalade": { ++ "version": "3.2.0", ++ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", ++ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6" ++ } ++ }, ++ "node_modules/escape-string-regexp": { ++ "version": "4.0.0", ++ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", ++ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" ++ } ++ }, ++ "node_modules/eslint": { ++ "version": "10.0.1", ++ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.1.tgz", ++ "integrity": "sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@eslint-community/eslint-utils": "^4.8.0", ++ "@eslint-community/regexpp": "^4.12.2", ++ "@eslint/config-array": "^0.23.2", ++ "@eslint/config-helpers": "^0.5.2", ++ "@eslint/core": "^1.1.0", ++ "@eslint/plugin-kit": "^0.6.0", ++ "@humanfs/node": "^0.16.6", ++ "@humanwhocodes/module-importer": "^1.0.1", ++ "@humanwhocodes/retry": "^0.4.2", ++ "@types/estree": "^1.0.6", ++ "ajv": "^6.12.4", ++ "cross-spawn": "^7.0.6", ++ "debug": "^4.3.2", ++ "escape-string-regexp": "^4.0.0", ++ "eslint-scope": "^9.1.1", ++ "eslint-visitor-keys": "^5.0.1", ++ "espree": "^11.1.1", ++ "esquery": "^1.7.0", ++ "esutils": "^2.0.2", ++ "fast-deep-equal": "^3.1.3", ++ "file-entry-cache": "^8.0.0", ++ "find-up": "^5.0.0", ++ "glob-parent": "^6.0.2", ++ "ignore": "^5.2.0", ++ "imurmurhash": "^0.1.4", ++ "is-glob": "^4.0.0", ++ "json-stable-stringify-without-jsonify": "^1.0.1", ++ "minimatch": "^10.2.1", ++ "natural-compare": "^1.4.0", ++ "optionator": "^0.9.3" ++ }, ++ "bin": { ++ "eslint": "bin/eslint.js" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ }, ++ "funding": { ++ "url": "https://eslint.org/donate" ++ }, ++ "peerDependencies": { ++ "jiti": "*" ++ }, ++ "peerDependenciesMeta": { ++ "jiti": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/eslint-plugin-react-hooks": { ++ "version": "7.1.1", ++ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", ++ "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@babel/core": "^7.24.4", ++ "@babel/parser": "^7.24.4", ++ "hermes-parser": "^0.25.1", ++ "zod": "^3.25.0 || ^4.0.0", ++ "zod-validation-error": "^3.5.0 || ^4.0.0" ++ }, ++ "engines": { ++ "node": ">=18" ++ }, ++ "peerDependencies": { ++ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" ++ } ++ }, ++ "node_modules/eslint-plugin-react-refresh": { ++ "version": "0.5.3", ++ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", ++ "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", ++ "dev": true, ++ "license": "MIT", ++ "peerDependencies": { ++ "eslint": "^9 || ^10" ++ } ++ }, ++ "node_modules/eslint-scope": { ++ "version": "9.1.2", ++ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", ++ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "dependencies": { ++ "@types/esrecurse": "^4.3.1", ++ "@types/estree": "^1.0.8", ++ "esrecurse": "^4.3.0", ++ "estraverse": "^5.2.0" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/eslint" ++ } ++ }, ++ "node_modules/eslint-visitor-keys": { ++ "version": "5.0.1", ++ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", ++ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/eslint" ++ } ++ }, ++ "node_modules/espree": { ++ "version": "11.2.0", ++ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", ++ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "dependencies": { ++ "acorn": "^8.16.0", ++ "acorn-jsx": "^5.3.2", ++ "eslint-visitor-keys": "^5.0.1" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.13.0 || >=24" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/eslint" ++ } ++ }, ++ "node_modules/esquery": { ++ "version": "1.7.0", ++ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", ++ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", ++ "dev": true, ++ "license": "BSD-3-Clause", ++ "dependencies": { ++ "estraverse": "^5.1.0" ++ }, ++ "engines": { ++ "node": ">=0.10" ++ } ++ }, ++ "node_modules/esrecurse": { ++ "version": "4.3.0", ++ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", ++ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "dependencies": { ++ "estraverse": "^5.2.0" ++ }, ++ "engines": { ++ "node": ">=4.0" ++ } ++ }, ++ "node_modules/estraverse": { ++ "version": "5.3.0", ++ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", ++ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "engines": { ++ "node": ">=4.0" ++ } ++ }, ++ "node_modules/estree-walker": { ++ "version": "3.0.3", ++ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", ++ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@types/estree": "^1.0.0" ++ } ++ }, ++ "node_modules/esutils": { ++ "version": "2.0.3", ++ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", ++ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, ++ "node_modules/eventemitter3": { ++ "version": "5.0.1", ++ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", ++ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", ++ "license": "MIT" ++ }, ++ "node_modules/expect-type": { ++ "version": "1.4.0", ++ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", ++ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": ">=12.0.0" ++ } ++ }, ++ "node_modules/fast-deep-equal": { ++ "version": "3.1.3", ++ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", ++ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/fast-json-stable-stringify": { ++ "version": "2.1.0", ++ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", ++ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/fast-levenshtein": { ++ "version": "2.0.6", ++ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", ++ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/fdir": { ++ "version": "6.5.0", ++ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", ++ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=12.0.0" ++ }, ++ "peerDependencies": { ++ "picomatch": "^3 || ^4" ++ }, ++ "peerDependenciesMeta": { ++ "picomatch": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/file-entry-cache": { ++ "version": "8.0.0", ++ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", ++ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "flat-cache": "^4.0.0" ++ }, ++ "engines": { ++ "node": ">=16.0.0" ++ } ++ }, ++ "node_modules/find-up": { ++ "version": "5.0.0", ++ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", ++ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "locate-path": "^6.0.0", ++ "path-exists": "^4.0.0" ++ }, ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" ++ } ++ }, ++ "node_modules/flat-cache": { ++ "version": "4.0.1", ++ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", ++ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "flatted": "^3.2.9", ++ "keyv": "^4.5.4" ++ }, ++ "engines": { ++ "node": ">=16" ++ } ++ }, ++ "node_modules/flatted": { ++ "version": "3.4.4", ++ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", ++ "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/fsevents": { ++ "version": "2.3.3", ++ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", ++ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", ++ "dev": true, ++ "hasInstallScript": true, ++ "license": "MIT", ++ "optional": true, ++ "os": [ ++ "darwin" ++ ], ++ "engines": { ++ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" ++ } ++ }, ++ "node_modules/gensync": { ++ "version": "1.0.0-beta.2", ++ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", ++ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6.9.0" ++ } ++ }, ++ "node_modules/glob-parent": { ++ "version": "6.0.2", ++ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", ++ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", ++ "dev": true, ++ "license": "ISC", ++ "dependencies": { ++ "is-glob": "^4.0.3" ++ }, ++ "engines": { ++ "node": ">=10.13.0" ++ } ++ }, ++ "node_modules/globals": { ++ "version": "17.7.0", ++ "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", ++ "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=18" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" ++ } ++ }, ++ "node_modules/hermes-estree": { ++ "version": "0.25.1", ++ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", ++ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/hermes-parser": { ++ "version": "0.25.1", ++ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", ++ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "hermes-estree": "0.25.1" ++ } ++ }, ++ "node_modules/html-encoding-sniffer": { ++ "version": "6.0.0", ++ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", ++ "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@exodus/bytes": "^1.6.0" ++ }, ++ "engines": { ++ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" ++ } ++ }, ++ "node_modules/ignore": { ++ "version": "5.3.2", ++ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", ++ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 4" ++ } ++ }, ++ "node_modules/imurmurhash": { ++ "version": "0.1.4", ++ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", ++ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=0.8.19" ++ } ++ }, ++ "node_modules/is-extglob": { ++ "version": "2.1.1", ++ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", ++ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, ++ "node_modules/is-glob": { ++ "version": "4.0.3", ++ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", ++ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "is-extglob": "^2.1.1" ++ }, ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, ++ "node_modules/is-potential-custom-element-name": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", ++ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/isexe": { ++ "version": "2.0.0", ++ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", ++ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/isows": { ++ "version": "1.0.7", ++ "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", ++ "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/wevm" ++ } ++ ], ++ "license": "MIT", ++ "peerDependencies": { ++ "ws": "*" ++ } ++ }, ++ "node_modules/js-tokens": { ++ "version": "4.0.0", ++ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", ++ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/jsdom": { ++ "version": "30.0.1", ++ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", ++ "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@asamuzakjp/css-color": "^6.0.5", ++ "@asamuzakjp/dom-selector": "^8.3.0", ++ "@bramus/specificity": "^2.4.2", ++ "@csstools/css-syntax-patches-for-csstree": "^1.1.7", ++ "@exodus/bytes": "^1.15.1", ++ "css-tree": "^3.2.1", ++ "data-urls": "^7.0.0", ++ "decimal.js": "^10.6.0", ++ "html-encoding-sniffer": "^6.0.0", ++ "is-potential-custom-element-name": "^1.0.1", ++ "lru-cache": "^11.5.2", ++ "parse5": "^8.0.1", ++ "saxes": "^6.0.0", ++ "symbol-tree": "^3.2.4", ++ "tough-cookie": "^6.0.2", ++ "undici": "^8.9.0", ++ "w3c-xmlserializer": "^5.0.0", ++ "webidl-conversions": "^8.0.1", ++ "whatwg-mimetype": "^5.0.0", ++ "whatwg-url": "^17.1.0", ++ "xml-name-validator": "^5.0.0" ++ }, ++ "engines": { ++ "node": "^22.22.2 || ^24.15.0 || >=26.0.0" ++ }, ++ "peerDependencies": { ++ "canvas": "^3.2.3" ++ }, ++ "peerDependenciesMeta": { ++ "canvas": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/jsdom/node_modules/lru-cache": { ++ "version": "11.5.2", ++ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", ++ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", ++ "dev": true, ++ "license": "BlueOak-1.0.0", ++ "engines": { ++ "node": "20 || >=22" ++ } ++ }, ++ "node_modules/jsesc": { ++ "version": "3.1.0", ++ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", ++ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", ++ "dev": true, ++ "license": "MIT", ++ "bin": { ++ "jsesc": "bin/jsesc" ++ }, ++ "engines": { ++ "node": ">=6" ++ } ++ }, ++ "node_modules/json-buffer": { ++ "version": "3.0.1", ++ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", ++ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/json-schema-traverse": { ++ "version": "0.4.1", ++ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", ++ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/json-stable-stringify-without-jsonify": { ++ "version": "1.0.1", ++ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", ++ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/json5": { ++ "version": "2.2.3", ++ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", ++ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", ++ "dev": true, ++ "license": "MIT", ++ "bin": { ++ "json5": "lib/cli.js" ++ }, ++ "engines": { ++ "node": ">=6" ++ } ++ }, ++ "node_modules/keyv": { ++ "version": "4.5.4", ++ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", ++ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "json-buffer": "3.0.1" ++ } ++ }, ++ "node_modules/levn": { ++ "version": "0.4.1", ++ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", ++ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "prelude-ls": "^1.2.1", ++ "type-check": "~0.4.0" ++ }, ++ "engines": { ++ "node": ">= 0.8.0" ++ } ++ }, ++ "node_modules/lightningcss": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", ++ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", ++ "dev": true, ++ "license": "MPL-2.0", ++ "dependencies": { ++ "detect-libc": "^2.0.3" ++ }, ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ }, ++ "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" ++ } ++ }, ++ "node_modules/lightningcss-android-arm64": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", ++ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "android" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-darwin-arm64": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", ++ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "darwin" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-darwin-x64": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", ++ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "darwin" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-freebsd-x64": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", ++ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "freebsd" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-linux-arm-gnueabihf": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", ++ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", ++ "cpu": [ ++ "arm" ++ ], ++ "dev": true, ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-linux-arm64-gnu": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", ++ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "libc": [ ++ "glibc" ++ ], ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-linux-arm64-musl": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", ++ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "libc": [ ++ "musl" ++ ], ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-linux-x64-gnu": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", ++ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "libc": [ ++ "glibc" ++ ], ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-linux-x64-musl": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", ++ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "libc": [ ++ "musl" ++ ], ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "linux" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-win32-arm64-msvc": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", ++ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", ++ "cpu": [ ++ "arm64" ++ ], ++ "dev": true, ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "win32" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/lightningcss-win32-x64-msvc": { ++ "version": "1.33.0", ++ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", ++ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", ++ "cpu": [ ++ "x64" ++ ], ++ "dev": true, ++ "license": "MPL-2.0", ++ "optional": true, ++ "os": [ ++ "win32" ++ ], ++ "engines": { ++ "node": ">= 12.0.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/parcel" ++ } ++ }, ++ "node_modules/locate-path": { ++ "version": "6.0.0", ++ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", ++ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "p-locate": "^5.0.0" ++ }, ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" ++ } ++ }, ++ "node_modules/lru-cache": { ++ "version": "5.1.1", ++ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", ++ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", ++ "dev": true, ++ "license": "ISC", ++ "dependencies": { ++ "yallist": "^3.0.2" ++ } ++ }, ++ "node_modules/lz-string": { ++ "version": "1.5.0", ++ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", ++ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", ++ "dev": true, ++ "license": "MIT", ++ "bin": { ++ "lz-string": "bin/bin.js" ++ } ++ }, ++ "node_modules/magic-string": { ++ "version": "0.30.21", ++ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", ++ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@jridgewell/sourcemap-codec": "^1.5.5" ++ } ++ }, ++ "node_modules/mdn-data": { ++ "version": "2.27.1", ++ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", ++ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", ++ "dev": true, ++ "license": "CC0-1.0" ++ }, ++ "node_modules/minimatch": { ++ "version": "10.2.6", ++ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", ++ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", ++ "dev": true, ++ "license": "BlueOak-1.0.0", ++ "dependencies": { ++ "brace-expansion": "^5.0.8" ++ }, ++ "engines": { ++ "node": "18 || 20 || >=22" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/isaacs" ++ } ++ }, ++ "node_modules/mipd": { ++ "version": "0.0.7", ++ "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz", ++ "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==", ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/wagmi-dev" ++ } ++ ], ++ "license": "MIT", ++ "peerDependencies": { ++ "typescript": ">=5.0.4" ++ }, ++ "peerDependenciesMeta": { ++ "typescript": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/ms": { ++ "version": "2.1.3", ++ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", ++ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/nanoid": { ++ "version": "3.3.18", ++ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", ++ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/ai" ++ } ++ ], ++ "license": "MIT", ++ "bin": { ++ "nanoid": "bin/nanoid.cjs" ++ }, ++ "engines": { ++ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" ++ } ++ }, ++ "node_modules/natural-compare": { ++ "version": "1.4.0", ++ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", ++ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/node-releases": { ++ "version": "2.0.53", ++ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", ++ "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=18" ++ } ++ }, ++ "node_modules/obug": { ++ "version": "2.1.4", ++ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", ++ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", ++ "dev": true, ++ "funding": [ ++ "https://github.com/sponsors/sxzz", ++ "https://opencollective.com/debug" ++ ], ++ "license": "MIT", ++ "engines": { ++ "node": ">=12.20.0" ++ } ++ }, ++ "node_modules/optionator": { ++ "version": "0.9.4", ++ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", ++ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "deep-is": "^0.1.3", ++ "fast-levenshtein": "^2.0.6", ++ "levn": "^0.4.1", ++ "prelude-ls": "^1.2.1", ++ "type-check": "^0.4.0", ++ "word-wrap": "^1.2.5" ++ }, ++ "engines": { ++ "node": ">= 0.8.0" ++ } ++ }, ++ "node_modules/ox": { ++ "version": "0.14.32", ++ "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.32.tgz", ++ "integrity": "sha512-EPB214GvtsP2TtAYZXkNdizLzGp6PXtfaHcRrD4pcBk/D0Y7ZCNv71QgwrjeCsZ+82moVeMlZZG+NDEIUfxMpw==", ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/wevm" ++ } ++ ], ++ "license": "MIT", ++ "dependencies": { ++ "@adraffy/ens-normalize": "^1.11.0", ++ "@noble/ciphers": "^1.3.0", ++ "@noble/curves": "1.9.1", ++ "@noble/hashes": "^1.8.0", ++ "@scure/bip32": "^1.7.0", ++ "@scure/bip39": "^1.6.0", ++ "abitype": "^1.2.3", ++ "eventemitter3": "5.0.1" ++ }, ++ "peerDependencies": { ++ "typescript": ">=5.4.0" ++ }, ++ "peerDependenciesMeta": { ++ "typescript": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/p-limit": { ++ "version": "3.1.0", ++ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", ++ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "yocto-queue": "^0.1.0" ++ }, ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" ++ } ++ }, ++ "node_modules/p-locate": { ++ "version": "5.0.0", ++ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", ++ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "p-limit": "^3.0.2" ++ }, ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" ++ } ++ }, ++ "node_modules/parse5": { ++ "version": "8.0.1", ++ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", ++ "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "entities": "^8.0.0" ++ }, ++ "funding": { ++ "url": "https://github.com/inikulin/parse5?sponsor=1" ++ } ++ }, ++ "node_modules/path-exists": { ++ "version": "4.0.0", ++ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", ++ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/path-key": { ++ "version": "3.1.1", ++ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", ++ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/pathe": { ++ "version": "2.0.3", ++ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", ++ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/picocolors": { ++ "version": "1.1.1", ++ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", ++ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/picomatch": { ++ "version": "4.0.5", ++ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", ++ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=12" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/jonschlinkert" ++ } ++ }, ++ "node_modules/postcss": { ++ "version": "8.5.26", ++ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", ++ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/postcss/" ++ }, ++ { ++ "type": "tidelift", ++ "url": "https://tidelift.com/funding/github/npm/postcss" ++ }, ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/ai" ++ } ++ ], ++ "license": "MIT", ++ "dependencies": { ++ "nanoid": "^3.3.17", ++ "picocolors": "^1.1.1", ++ "source-map-js": "^1.2.1" ++ }, ++ "engines": { ++ "node": "^10 || ^12 || >=14" ++ } ++ }, ++ "node_modules/prelude-ls": { ++ "version": "1.2.1", ++ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", ++ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">= 0.8.0" ++ } ++ }, ++ "node_modules/pretty-format": { ++ "version": "27.5.1", ++ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", ++ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "ansi-regex": "^5.0.1", ++ "ansi-styles": "^5.0.0", ++ "react-is": "^17.0.1" ++ }, ++ "engines": { ++ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" ++ } ++ }, ++ "node_modules/punycode": { ++ "version": "2.3.1", ++ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", ++ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=6" ++ } ++ }, ++ "node_modules/react": { ++ "version": "19.2.8", ++ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", ++ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, ++ "node_modules/react-dom": { ++ "version": "19.2.8", ++ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", ++ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", ++ "license": "MIT", ++ "dependencies": { ++ "scheduler": "^0.27.0" ++ }, ++ "peerDependencies": { ++ "react": "^19.2.8" ++ } ++ }, ++ "node_modules/react-is": { ++ "version": "17.0.2", ++ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", ++ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/require-from-string": { ++ "version": "2.0.2", ++ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", ++ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, ++ "node_modules/rolldown": { ++ "version": "1.2.4", ++ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", ++ "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@oxc-project/types": "=0.144.0", ++ "@rolldown/pluginutils": "^1.0.0" ++ }, ++ "bin": { ++ "rolldown": "bin/cli.mjs" ++ }, ++ "engines": { ++ "node": "^20.19.0 || >=22.12.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" ++ } ++ }, ++ "node_modules/saxes": { ++ "version": "6.0.0", ++ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", ++ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", ++ "dev": true, ++ "license": "ISC", ++ "dependencies": { ++ "xmlchars": "^2.2.0" ++ }, ++ "engines": { ++ "node": ">=v12.22.7" ++ } ++ }, ++ "node_modules/scheduler": { ++ "version": "0.27.0", ++ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", ++ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", ++ "license": "MIT" ++ }, ++ "node_modules/semver": { ++ "version": "6.3.1", ++ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", ++ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", ++ "dev": true, ++ "license": "ISC", ++ "bin": { ++ "semver": "bin/semver.js" ++ } ++ }, ++ "node_modules/shebang-command": { ++ "version": "2.0.0", ++ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", ++ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "shebang-regex": "^3.0.0" ++ }, ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/shebang-regex": { ++ "version": "3.0.0", ++ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", ++ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/siginfo": { ++ "version": "2.0.0", ++ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", ++ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/source-map-js": { ++ "version": "1.2.1", ++ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", ++ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", ++ "dev": true, ++ "license": "BSD-3-Clause", ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, ++ "node_modules/stackback": { ++ "version": "0.0.2", ++ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", ++ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/std-env": { ++ "version": "4.2.0", ++ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", ++ "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/symbol-tree": { ++ "version": "3.2.4", ++ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", ++ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/tinybench": { ++ "version": "2.9.0", ++ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", ++ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/tinyexec": { ++ "version": "1.3.0", ++ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", ++ "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=18" ++ } ++ }, ++ "node_modules/tinyglobby": { ++ "version": "0.2.17", ++ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", ++ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "fdir": "^6.5.0", ++ "picomatch": "^4.0.4" ++ }, ++ "engines": { ++ "node": ">=12.0.0" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/SuperchupuDev" ++ } ++ }, ++ "node_modules/tinyrainbow": { ++ "version": "3.1.1", ++ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", ++ "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=14.0.0" ++ } ++ }, ++ "node_modules/tldts": { ++ "version": "7.4.10", ++ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", ++ "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "tldts-core": "^7.4.10" ++ }, ++ "bin": { ++ "tldts": "bin/cli.js" ++ } ++ }, ++ "node_modules/tldts-core": { ++ "version": "7.4.10", ++ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", ++ "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/tough-cookie": { ++ "version": "6.0.2", ++ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", ++ "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", ++ "dev": true, ++ "license": "BSD-3-Clause", ++ "dependencies": { ++ "tldts": "^7.0.5" ++ }, ++ "engines": { ++ "node": ">=16" ++ } ++ }, ++ "node_modules/tr46": { ++ "version": "6.0.0", ++ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", ++ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "punycode": "^2.3.1" ++ }, ++ "engines": { ++ "node": ">=20" ++ } ++ }, ++ "node_modules/ts-api-utils": { ++ "version": "2.5.0", ++ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", ++ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=18.12" ++ }, ++ "peerDependencies": { ++ "typescript": ">=4.8.4" ++ } ++ }, ++ "node_modules/type-check": { ++ "version": "0.4.0", ++ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", ++ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "prelude-ls": "^1.2.1" ++ }, ++ "engines": { ++ "node": ">= 0.8.0" ++ } ++ }, ++ "node_modules/typescript": { ++ "name": "@typescript/typescript6", ++ "version": "6.0.2", ++ "resolved": "https://registry.npmjs.org/@typescript/typescript6/-/typescript6-6.0.2.tgz", ++ "integrity": "sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==", ++ "devOptional": true, ++ "license": "Apache-2.0", ++ "dependencies": { ++ "@typescript/old": "npm:typescript@^6" ++ }, ++ "bin": { ++ "tsc6": "bin/tsc6" ++ } ++ }, ++ "node_modules/typescript-eslint": { ++ "version": "8.65.0", ++ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", ++ "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@typescript-eslint/eslint-plugin": "8.65.0", ++ "@typescript-eslint/parser": "8.65.0", ++ "@typescript-eslint/typescript-estree": "8.65.0", ++ "@typescript-eslint/utils": "8.65.0" ++ }, ++ "engines": { ++ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" ++ }, ++ "funding": { ++ "type": "opencollective", ++ "url": "https://opencollective.com/typescript-eslint" ++ }, ++ "peerDependencies": { ++ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", ++ "typescript": ">=4.8.4 <6.1.0" ++ } ++ }, ++ "node_modules/undici": { ++ "version": "8.10.0", ++ "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", ++ "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=22.19.0" ++ } ++ }, ++ "node_modules/undici-types": { ++ "version": "7.16.0", ++ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", ++ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/update-browserslist-db": { ++ "version": "1.3.1", ++ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", ++ "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", ++ "dev": true, ++ "funding": [ ++ { ++ "type": "opencollective", ++ "url": "https://opencollective.com/browserslist" ++ }, ++ { ++ "type": "tidelift", ++ "url": "https://tidelift.com/funding/github/npm/browserslist" ++ }, ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/ai" ++ } ++ ], ++ "license": "MIT", ++ "dependencies": { ++ "escalade": "^3.2.0", ++ "picocolors": "^1.1.1" ++ }, ++ "bin": { ++ "update-browserslist-db": "cli.js" ++ }, ++ "peerDependencies": { ++ "browserslist": ">= 4.21.0" ++ } ++ }, ++ "node_modules/uri-js": { ++ "version": "4.4.1", ++ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", ++ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "dependencies": { ++ "punycode": "^2.1.0" ++ } ++ }, ++ "node_modules/use-sync-external-store": { ++ "version": "1.4.0", ++ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", ++ "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", ++ "license": "MIT", ++ "peerDependencies": { ++ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ++ } ++ }, ++ "node_modules/viem": { ++ "version": "2.55.8", ++ "resolved": "https://registry.npmjs.org/viem/-/viem-2.55.8.tgz", ++ "integrity": "sha512-BHqtsmK4iMLuLnRyrPIB1jVrmFVliRIP/K0dnFT7gBOpfo8Ko4ozhkzUCRNfR+Z/ZZdnlnVrh04fAOuIm5Svkg==", ++ "funding": [ ++ { ++ "type": "github", ++ "url": "https://github.com/sponsors/wevm" ++ } ++ ], ++ "license": "MIT", ++ "dependencies": { ++ "@noble/curves": "1.9.1", ++ "@noble/hashes": "1.8.0", ++ "@scure/bip32": "1.7.0", ++ "@scure/bip39": "1.6.0", ++ "abitype": "1.2.3", ++ "isows": "1.0.7", ++ "ox": "0.14.32", ++ "ws": "8.21.0" ++ }, ++ "peerDependencies": { ++ "typescript": ">=5.0.4" ++ }, ++ "peerDependenciesMeta": { ++ "typescript": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/vite": { ++ "version": "8.2.0", ++ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", ++ "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "lightningcss": "^1.33.0", ++ "picomatch": "^4.0.5", ++ "postcss": "^8.5.23", ++ "rolldown": "~1.2.0", ++ "tinyglobby": "^0.2.17" ++ }, ++ "bin": { ++ "vite": "bin/vite.js" ++ }, ++ "engines": { ++ "node": "^20.19.0 || >=22.12.0" ++ }, ++ "funding": { ++ "url": "https://github.com/vitejs/vite?sponsor=1" ++ }, ++ "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" ++ }, ++ "peerDependenciesMeta": { ++ "@types/node": { ++ "optional": true ++ }, ++ "@vitejs/devtools": { ++ "optional": true ++ }, ++ "esbuild": { ++ "optional": true ++ }, ++ "jiti": { ++ "optional": true ++ }, ++ "less": { ++ "optional": true ++ }, ++ "sass": { ++ "optional": true ++ }, ++ "sass-embedded": { ++ "optional": true ++ }, ++ "stylus": { ++ "optional": true ++ }, ++ "sugarss": { ++ "optional": true ++ }, ++ "terser": { ++ "optional": true ++ }, ++ "tsx": { ++ "optional": true ++ }, ++ "yaml": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/vitest": { ++ "version": "4.1.10", ++ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", ++ "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", ++ "dev": true, ++ "license": "MIT", ++ "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" ++ }, ++ "bin": { ++ "vitest": "vitest.mjs" ++ }, ++ "engines": { ++ "node": "^20.0.0 || ^22.0.0 || >=24.0.0" ++ }, ++ "funding": { ++ "url": "https://opencollective.com/vitest" ++ }, ++ "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": "*", ++ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" ++ }, ++ "peerDependenciesMeta": { ++ "@edge-runtime/vm": { ++ "optional": true ++ }, ++ "@opentelemetry/api": { ++ "optional": true ++ }, ++ "@types/node": { ++ "optional": true ++ }, ++ "@vitest/browser-playwright": { ++ "optional": true ++ }, ++ "@vitest/browser-preview": { ++ "optional": true ++ }, ++ "@vitest/browser-webdriverio": { ++ "optional": true ++ }, ++ "@vitest/coverage-istanbul": { ++ "optional": true ++ }, ++ "@vitest/coverage-v8": { ++ "optional": true ++ }, ++ "@vitest/ui": { ++ "optional": true ++ }, ++ "happy-dom": { ++ "optional": true ++ }, ++ "jsdom": { ++ "optional": true ++ }, ++ "vite": { ++ "optional": false ++ } ++ } ++ }, ++ "node_modules/w3c-xmlserializer": { ++ "version": "5.0.0", ++ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", ++ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "xml-name-validator": "^5.0.0" ++ }, ++ "engines": { ++ "node": ">=18" ++ } ++ }, ++ "node_modules/wagmi": { ++ "version": "3.7.5", ++ "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-3.7.5.tgz", ++ "integrity": "sha512-icXdl/fLp6sQlZMDFZX6EazkzR8dBDSjJfYrN96A0tJeEYww++TUHva7oUzOU49k/guV6u+rUpYTjc2egC+hLA==", ++ "license": "MIT", ++ "dependencies": { ++ "@wagmi/connectors": "8.0.26", ++ "@wagmi/core": "3.6.4", ++ "use-sync-external-store": "1.4.0" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/wevm" ++ }, ++ "peerDependencies": { ++ "@tanstack/react-query": ">=5.0.0", ++ "react": ">=18", ++ "typescript": ">=5.9.3", ++ "viem": "2.x" ++ }, ++ "peerDependenciesMeta": { ++ "typescript": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/webidl-conversions": { ++ "version": "8.0.1", ++ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", ++ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", ++ "dev": true, ++ "license": "BSD-2-Clause", ++ "engines": { ++ "node": ">=20" ++ } ++ }, ++ "node_modules/whatwg-mimetype": { ++ "version": "5.0.0", ++ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", ++ "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=20" ++ } ++ }, ++ "node_modules/whatwg-url": { ++ "version": "17.1.0", ++ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", ++ "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "@exodus/bytes": "^1.15.1", ++ "tr46": "^6.0.0", ++ "webidl-conversions": "^8.0.1" ++ }, ++ "engines": { ++ "node": "^22.14.0 || >=24.0.0" ++ } ++ }, ++ "node_modules/which": { ++ "version": "2.0.2", ++ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", ++ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", ++ "dev": true, ++ "license": "ISC", ++ "dependencies": { ++ "isexe": "^2.0.0" ++ }, ++ "bin": { ++ "node-which": "bin/node-which" ++ }, ++ "engines": { ++ "node": ">= 8" ++ } ++ }, ++ "node_modules/why-is-node-running": { ++ "version": "2.3.0", ++ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", ++ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", ++ "dev": true, ++ "license": "MIT", ++ "dependencies": { ++ "siginfo": "^2.0.0", ++ "stackback": "0.0.2" ++ }, ++ "bin": { ++ "why-is-node-running": "cli.js" ++ }, ++ "engines": { ++ "node": ">=8" ++ } ++ }, ++ "node_modules/word-wrap": { ++ "version": "1.2.5", ++ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", ++ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=0.10.0" ++ } ++ }, ++ "node_modules/ws": { ++ "version": "8.21.0", ++ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", ++ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=10.0.0" ++ }, ++ "peerDependencies": { ++ "bufferutil": "^4.0.1", ++ "utf-8-validate": ">=5.0.2" ++ }, ++ "peerDependenciesMeta": { ++ "bufferutil": { ++ "optional": true ++ }, ++ "utf-8-validate": { ++ "optional": true ++ } ++ } ++ }, ++ "node_modules/xml-name-validator": { ++ "version": "5.0.0", ++ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", ++ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", ++ "dev": true, ++ "license": "Apache-2.0", ++ "engines": { ++ "node": ">=18" ++ } ++ }, ++ "node_modules/xmlchars": { ++ "version": "2.2.0", ++ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", ++ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", ++ "dev": true, ++ "license": "MIT" ++ }, ++ "node_modules/yallist": { ++ "version": "3.1.1", ++ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", ++ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", ++ "dev": true, ++ "license": "ISC" ++ }, ++ "node_modules/yocto-queue": { ++ "version": "0.1.0", ++ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", ++ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=10" ++ }, ++ "funding": { ++ "url": "https://github.com/sponsors/sindresorhus" ++ } ++ }, ++ "node_modules/zod": { ++ "version": "4.4.3", ++ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", ++ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", ++ "devOptional": true, ++ "license": "MIT", ++ "funding": { ++ "url": "https://github.com/sponsors/colinhacks" ++ } ++ }, ++ "node_modules/zod-validation-error": { ++ "version": "4.0.2", ++ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", ++ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", ++ "dev": true, ++ "license": "MIT", ++ "engines": { ++ "node": ">=18.0.0" ++ }, ++ "peerDependencies": { ++ "zod": "^3.25.0 || ^4.0.0" ++ } ++ }, ++ "node_modules/zustand": { ++ "version": "5.0.0", ++ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz", ++ "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==", ++ "license": "MIT", ++ "engines": { ++ "node": ">=12.20.0" ++ }, ++ "peerDependencies": { ++ "@types/react": ">=18.0.0", ++ "immer": ">=9.0.6", ++ "react": ">=18.0.0", ++ "use-sync-external-store": ">=1.2.0" ++ }, ++ "peerDependenciesMeta": { ++ "@types/react": { ++ "optional": true ++ }, ++ "immer": { ++ "optional": true ++ }, ++ "react": { ++ "optional": true ++ }, ++ "use-sync-external-store": { ++ "optional": true ++ } ++ } ++ } ++ } ++} +diff --git a/web/package.json b/web/package.json +new file mode 100644 +index 0000000..46337c0 +--- /dev/null ++++ b/web/package.json +@@ -0,0 +1,41 @@ ++{ ++ "name": "uups-bank-operations-console", ++ "private": true, ++ "version": "0.1.0", ++ "type": "module", ++ "packageManager": "npm@11.17.0", ++ "engines": { "node": ">=24.18.0 <25", "npm": ">=11.17.0 <12" }, ++ "scripts": { ++ "dev": "vite --host 127.0.0.1", ++ "lint": "eslint . --max-warnings 0", ++ "typecheck": "node ./node_modules/@typescript/native/bin/tsc -b --pretty false", ++ "test": "vitest run", ++ "build": "node ./node_modules/@typescript/native/bin/tsc -b && vite build" ++ }, ++ "dependencies": { ++ "@tanstack/react-query": "5.101.4", ++ "react": "19.2.8", ++ "react-dom": "19.2.8", ++ "viem": "2.55.8", ++ "wagmi": "3.7.5" ++ }, ++ "devDependencies": { ++ "@eslint/js": "10.0.1", ++ "@testing-library/dom": "10.4.1", ++ "@testing-library/react": "16.3.2", ++ "@types/node": "24.10.0", ++ "@types/react": "19.2.14", ++ "@types/react-dom": "19.2.4", ++ "@typescript/native": "npm:typescript@7.0.2", ++ "@vitejs/plugin-react": "6.0.4", ++ "eslint": "10.0.1", ++ "eslint-plugin-react-hooks": "7.1.1", ++ "eslint-plugin-react-refresh": "0.5.3", ++ "globals": "17.7.0", ++ "jsdom": "30.0.1", ++ "typescript": "npm:@typescript/typescript6@6.0.2", ++ "typescript-eslint": "8.65.0", ++ "vite": "8.2.0", ++ "vitest": "4.1.10" ++ } ++} +diff --git a/web/src/config/toolchain.ts b/web/src/config/toolchain.ts +new file mode 100644 +index 0000000..46852ce +--- /dev/null ++++ b/web/src/config/toolchain.ts +@@ -0,0 +1,6 @@ ++export const toolchainLabels = { ++ foundry: "Foundry 1.7.1", ++ solidity: "Solidity 0.8.35", ++ openZeppelin: "OpenZeppelin 5.6.1", ++ proxy: "UUPS" ++}; +diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts +new file mode 100644 +index 0000000..cb0ff5c +--- /dev/null ++++ b/web/src/test/setup.ts +@@ -0,0 +1 @@ ++export {}; +diff --git a/web/src/test/toolchain.test.ts b/web/src/test/toolchain.test.ts +new file mode 100644 +index 0000000..f8ffc03 +--- /dev/null ++++ b/web/src/test/toolchain.test.ts +@@ -0,0 +1,13 @@ ++import { describe, expect, it } from "vitest"; ++import { toolchainLabels } from "../config/toolchain"; ++ ++describe("toolchain labels", () => { ++ it("exposes the versions shown by the operations console", () => { ++ expect(toolchainLabels).toEqual({ ++ foundry: "Foundry 1.7.1", ++ solidity: "Solidity 0.8.35", ++ openZeppelin: "OpenZeppelin 5.6.1", ++ proxy: "UUPS" ++ }); ++ }); ++}); +diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json +new file mode 100644 +index 0000000..fbbf81f +--- /dev/null ++++ b/web/tsconfig.app.json +@@ -0,0 +1,20 @@ ++{ ++ "compilerOptions": { ++ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", ++ "target": "ES2022", ++ "useDefineForClassFields": true, ++ "lib": ["ES2022", "DOM", "DOM.Iterable"], ++ "allowJs": false, ++ "skipLibCheck": true, ++ "esModuleInterop": true, ++ "allowSyntheticDefaultImports": true, ++ "strict": true, ++ "module": "ESNext", ++ "moduleResolution": "bundler", ++ "resolveJsonModule": true, ++ "isolatedModules": true, ++ "noEmit": true, ++ "jsx": "react-jsx" ++ }, ++ "include": ["src"] ++} +diff --git a/web/tsconfig.json b/web/tsconfig.json +new file mode 100644 +index 0000000..1ffef60 +--- /dev/null ++++ b/web/tsconfig.json +@@ -0,0 +1,7 @@ ++{ ++ "files": [], ++ "references": [ ++ { "path": "./tsconfig.app.json" }, ++ { "path": "./tsconfig.node.json" } ++ ] ++} +diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json +new file mode 100644 +index 0000000..b64bd9f +--- /dev/null ++++ b/web/tsconfig.node.json +@@ -0,0 +1,16 @@ ++{ ++ "compilerOptions": { ++ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", ++ "target": "ES2023", ++ "lib": ["ES2023"], ++ "module": "ESNext", ++ "moduleResolution": "bundler", ++ "allowImportingTsExtensions": true, ++ "verbatimModuleSyntax": true, ++ "moduleDetection": "force", ++ "noEmit": true, ++ "skipLibCheck": true, ++ "strict": true ++ }, ++ "include": ["vite.config.ts"] ++} +diff --git a/web/vite.config.ts b/web/vite.config.ts +new file mode 100644 +index 0000000..1619764 +--- /dev/null ++++ b/web/vite.config.ts +@@ -0,0 +1,11 @@ ++import react from "@vitejs/plugin-react"; ++import { defineConfig } from "vitest/config"; ++ ++export default defineConfig({ ++ plugins: [react()], ++ test: { ++ environment: "jsdom", ++ globals: true, ++ setupFiles: ["./src/test/setup.ts"] ++ } ++}); diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-1f4175b..0664eb4.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-1f4175b..0664eb4.diff new file mode 100644 index 0000000..a815caf --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-1f4175b..0664eb4.diff @@ -0,0 +1,311 @@ +# Review package: 1f4175ba729fbbab58538cd3ac02b5459e014c86..0664eb4688bd236e2678c5dda2f4a10e898205ed + +## Commits +0664eb4 feat: establish UUPS bank V1 + +## Files changed + .../superpowers/plans/2026-08-17-uups-bank-demo.md | 10 +-- + src/BankV1.sol | 60 ++++++++++++++ + test/BankV1Admin.t.sol | 94 ++++++++++++++++++++++ + test/helpers/BankTestBase.sol | 33 ++++++++ + 4 files changed, 192 insertions(+), 5 deletions(-) + +## Diff +diff --git a/docs/superpowers/plans/2026-08-17-uups-bank-demo.md b/docs/superpowers/plans/2026-08-17-uups-bank-demo.md +index 3a3ca8f..cfaaaa6 100644 +--- a/docs/superpowers/plans/2026-08-17-uups-bank-demo.md ++++ b/docs/superpowers/plans/2026-08-17-uups-bank-demo.md +@@ -7,21 +7,21 @@ + **Architecture:** Foundry scripts are the only state-changing control plane. A six-decimal `MockUSDC` and a UUPS `BankV1` implementation run behind a stable ERC-1967 proxy on Anvil or optional Base Sepolia. Scripts export a public deployment manifest and contract ABIs; a wagmi/viem client reads those artifacts and renders reserves, liabilities, identities, actors, and events without a signer or wallet connector. V2 inherits V1, adds no storage, and adds one internal transfer function. + + **Tech Stack:** Foundry `v1.7.1`, forge-std `v1.16.1`, Solidity `0.8.35`, OpenZeppelin Contracts Upgradeable `v5.6.1`, OpenZeppelin Foundry Upgrades `v0.4.1`, OpenZeppelin Upgrades Core `1.46.0`, Node `24.18.0`, npm `11.17.0`, React `19.2.8`, TypeScript compiler `7.0.2` via `@typescript/native`, TypeScript API compatibility `6.0.2` via the `typescript` alias, Vite `8.2.0`, wagmi `3.7.5`, viem `2.55.8`, TanStack Query `5.101.4`, Vitest `4.1.10`, Testing Library React `16.3.2`, and jsdom `30.0.1`. + + ## Global Constraints + + - The contracts are educational and unaudited. Every user-facing surface says: `Educational demo — mock token — never use real funds.` + - State-changing scripts accept only chain IDs `31337` and `84532`; no mainnet RPC, address, target, or configuration is added. + - Local accounts come only from Anvil’s standard development mnemonic. Testnet signing uses a named encrypted Foundry keystore and `--account`; no supported command accepts a raw private key or mnemonic environment variable. + - Every application call uses the proxy address. The implementation address is read only for validation and explanation. +-- `Upgrades`, never `UnsafeUpgrades`, performs deploy/upgrade validation. The sole validator allowance is the constructor annotation immediately above `_disableInitializers()`. ++- `Upgrades`, never `UnsafeUpgrades`, performs deploy/upgrade validation. The sole validator allowance is `@custom:oz-upgrades-unsafe-allow constructor` immediately above the constructor that calls `_disableInitializers()`; no Options `unsafeAllow`, exclude, skip, `UnsafeUpgrades`, reachable annotation, or other bypass is permitted. BankV1 uses OpenZeppelin 5.6.1 `ReentrancyGuardTransient`, the user-approved constructor-free guard for this project's Cancun-targeted Anvil and Base Sepolia networks. + - V2 does not add, delete, reorder, or change the type of any storage variable and has no initializer. + - The browser has no connector, signer, transaction client, or write button. Failed reads remain unknown; they are never rendered as zero. + - Generated Foundry output, local manifests, copied web artifacts, `.demo/` process state, `.env`, and `.superpowers/` are ignored by Git. + - Run each red/green command exactly as written. A test that unexpectedly passes in a red step means the test is not proving the intended behavior; fix the test before implementation. + - Use exact versions and committed lockfiles/submodule revisions. Do not replace exact versions with ranges. + - OpenZeppelin Foundry Upgrades `v0.4.1` invokes `npx @openzeppelin/upgrades-core@^1.45.0`; the root lockfile pins the satisfying implementation to `1.46.0`, and every validation-bearing command runs with `npm_config_offline=true` after setup proves the local CLI is available. + - Commit only files listed for the task and inspect `git status --short` before every commit so unrelated user changes remain untouched. + + ## File and Interface Map + +@@ -367,39 +367,39 @@ All bank calls in tests target `proxy`; retain `implementation = Upgrades.getImp + npm_config_offline=true forge test --match-path test/BankV1Admin.t.sol -vvv --force + ``` + + Expected red: `BankV1` is missing. + + - [ ] Implement the minimum V1 skeleton with these imports and inheritance order: + + ```solidity + import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +-import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; ++import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; + import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; + + contract BankV1 is + Initializable, + UUPSUpgradeable, + OwnableUpgradeable, + PausableUpgradeable, +- ReentrancyGuard ++ ReentrancyGuardTransient + { + IERC20 internal _asset; + mapping(address account => uint256 balance) internal _balances; + uint256 internal _totalLiabilities; + uint256[47] private __gap; + } + ``` + +-Use `error InvalidAsset(address asset);`. The initializer checks the asset before calling only `__Ownable_init(initialOwner)` and `__Pausable_init()`. `Initializable`, `UUPSUpgradeable`, and `ReentrancyGuard` are stateless/shared in pinned OpenZeppelin 5.6.1 and have no initializer calls. ++Use `error InvalidAsset(address asset);`. The initializer checks the asset before calling only `__Ownable_init(initialOwner)` and `__Pausable_init()`. `Initializable`, `UUPSUpgradeable`, and `ReentrancyGuardTransient` are stateless/shared in pinned OpenZeppelin 5.6.1 and have no initializer calls. The transient guard is required by the user-approved final design because it is constructor-free and the project's supported Anvil and Base Sepolia networks target Cancun/EIP-1153. + + - [ ] Add the only permitted validator annotation and no other bypass: + + ```solidity + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + ``` + +@@ -1074,21 +1074,21 @@ reset-local: + ``` + + Retain the explicit `deploy-v1`, `seed-v1`, and `sync-artifacts` lower-level targets for teaching and recovery. + + - [ ] Write `README.md` with prerequisites, `make setup`, the ten-minute `make demo-local` quick start, second-terminal commands, architecture paragraph, exact URLs, command reference, and links to both guides. Its trust box must say the token has no value; code is educational/unaudited; real deposits must never be sent; the owner can pause and install arbitrary future logic; UUPS mistakes can corrupt state or brick upgrades; and a real custody product needs professional audits, operational key controls, multisig/timelocked governance, incident procedures, legal advice, and jurisdiction-specific compliance. + + - [ ] Write the V1-complete `LEARNING_GUIDE.md`: proxy/implementation/delegatecall/storage, constructor vs initializer, disabled implementation initialization, safe/unsafe storage examples, reserves/liabilities/surplus, exact deposit/withdraw flow, SafeERC20, pause, reentrancy, checks-effects-interactions, single-owner arbitrary-upgrade power, UUPS corruption/bricking risk, the complete real-product safeguards disclosure, and local exercises that trigger each named error. + + - [ ] Write the prepared portion of `PRESENTER_RUNBOOK.md`: preflight, rehearsal timing, exact three-act story, the approved live Codex prompt verbatim, Act 1 expected state, commands/console callouts, occupied-port/stale-console/test-failure recovery, and the rule that no upgrade runs until `make verify` passes. Describe Act 2/3 expected outcomes without including reference V2 source, and include a closing trust disclosure checklist matching README/UI word-for-word in substance. + +-- [ ] Implement `scan-project.sh` with a NUL-safe array from `git ls-files`, excluding dependency gitlinks, `docs/superpowers`, and generated lockfiles. Return nonzero if project-owned tracked content contains an actual `PRIVATE_KEY`/`MNEMONIC` assignment, PEM private key, `TODO`, `TBD`, `FIXME`, filler text, or an unsafe upgrade bypass in `src`/`test`/`script`. Construct the scanner’s own pattern from split shell literals so it does not match itself. Permit only the exact hyphenated constructor annotation. The committed `ANVIL_TEST_PHRASE` is the universally known test fixture, not a supported secret input; test that exact fixture is the only scan exception. ++- [ ] Implement `scan-project.sh` with a NUL-safe array from `git ls-files`, excluding dependency gitlinks, `docs/superpowers`, and generated lockfiles. Return nonzero if project-owned tracked content contains an actual `PRIVATE_KEY`/`MNEMONIC` assignment, PEM private key, `TODO`, `TBD`, `FIXME`, filler text, or an unsafe upgrade bypass in `src`/`test`/`script`. Construct the scanner’s own pattern from split shell literals so it does not match itself. Permit only the exact hyphenated `oz-upgrades-unsafe-allow constructor` annotation approved for `BankV1`. The committed `ANVIL_TEST_PHRASE` is the universally known test fixture, not a supported secret input; test that exact fixture is the only scan exception. + + - [ ] Run process and full verification: + + ```bash + bash tools/test-process-safety.sh + make doctor + make verify + ``` + + Expected: all checks exit `0`. +diff --git a/src/BankV1.sol b/src/BankV1.sol +new file mode 100644 +index 0000000..13ffca5 +--- /dev/null ++++ b/src/BankV1.sol +@@ -0,0 +1,60 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; ++import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; ++import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; ++import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; ++import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; ++import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; ++ ++contract BankV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardTransient { ++ error InvalidAsset(address asset); ++ ++ IERC20 internal _asset; ++ mapping(address account => uint256 balance) internal _balances; ++ uint256 internal _totalLiabilities; ++ uint256[47] private __gap; ++ ++ /// @custom:oz-upgrades-unsafe-allow constructor ++ constructor() { ++ _disableInitializers(); ++ } ++ ++ function initialize(address asset_, address initialOwner) external initializer { ++ if (asset_ == address(0)) { ++ revert InvalidAsset(asset_); ++ } ++ ++ __Ownable_init(initialOwner); ++ __Pausable_init(); ++ ++ _asset = IERC20(asset_); ++ } ++ ++ function pause() external onlyOwner { ++ _pause(); ++ } ++ ++ function unpause() external onlyOwner { ++ _unpause(); ++ } ++ ++ function asset() external view returns (IERC20) { ++ return _asset; ++ } ++ ++ function balanceOf(address account) external view returns (uint256) { ++ return _balances[account]; ++ } ++ ++ function totalLiabilities() external view returns (uint256) { ++ return _totalLiabilities; ++ } ++ ++ function contractVersion() public pure virtual returns (uint256) { ++ return 1; ++ } ++ ++ function _authorizeUpgrade(address) internal override onlyOwner {} ++} +diff --git a/test/BankV1Admin.t.sol b/test/BankV1Admin.t.sol +new file mode 100644 +index 0000000..7e3c26c +--- /dev/null ++++ b/test/BankV1Admin.t.sol +@@ -0,0 +1,94 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; ++import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; ++import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; ++import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; ++ ++import {BankV1} from "../src/BankV1.sol"; ++import {BankTestBase} from "./helpers/BankTestBase.sol"; ++ ++contract BankV1AdminTest is BankTestBase { ++ bytes32 private constant ERC1967_IMPLEMENTATION_SLOT = ++ 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; ++ ++ function testProxyStartsWithConfiguredAdministrationAndEmptyAccounting() public view { ++ assertEq(address(bank.asset()), address(token)); ++ assertEq(bank.owner(), owner); ++ assertFalse(bank.paused()); ++ assertEq(bank.balanceOf(alice), 0); ++ assertEq(bank.totalLiabilities(), 0); ++ assertEq(bank.contractVersion(), 1); ++ } ++ ++ function testInitializationChecksZeroAssetBeforeZeroOwner() public { ++ vm.expectRevert(abi.encodeWithSelector(BankV1.InvalidAsset.selector, address(0))); ++ new ERC1967Proxy(implementation, abi.encodeCall(BankV1.initialize, (address(0), address(0)))); ++ } ++ ++ function testInitializationRejectsZeroOwner() public { ++ vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableInvalidOwner.selector, address(0))); ++ new ERC1967Proxy(implementation, abi.encodeCall(BankV1.initialize, (address(token), address(0)))); ++ } ++ ++ function testProxyCannotBeInitializedTwice() public { ++ vm.expectRevert(Initializable.InvalidInitialization.selector); ++ bank.initialize(address(token), owner); ++ } ++ ++ function testImplementationCannotBeInitialized() public { ++ vm.expectRevert(Initializable.InvalidInitialization.selector); ++ BankV1(implementation).initialize(address(token), owner); ++ } ++ ++ function testOwnerCanPauseAndUnpause() public { ++ vm.prank(owner); ++ bank.pause(); ++ assertTrue(bank.paused()); ++ ++ vm.prank(owner); ++ bank.unpause(); ++ assertFalse(bank.paused()); ++ } ++ ++ function testNonOwnerCannotPause() public { ++ vm.prank(stranger); ++ vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, stranger)); ++ bank.pause(); ++ } ++ ++ function testNonOwnerCannotUnpause() public { ++ vm.prank(owner); ++ bank.pause(); ++ ++ vm.prank(stranger); ++ vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, stranger)); ++ bank.unpause(); ++ } ++ ++ function testViewsRemainAvailableWhilePaused() public { ++ vm.prank(owner); ++ bank.pause(); ++ ++ assertEq(address(bank.asset()), address(token)); ++ assertEq(bank.balanceOf(alice), 0); ++ assertEq(bank.totalLiabilities(), 0); ++ assertEq(bank.contractVersion(), 1); ++ } ++ ++ function testNonOwnerCannotAuthorizeUpgrade() public { ++ vm.prank(stranger); ++ vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, stranger)); ++ bank.upgradeToAndCall(implementation, ""); ++ } ++ ++ function testImplementationExposesERC1967ProxiableUUID() public view { ++ assertEq(BankV1(implementation).proxiableUUID(), ERC1967_IMPLEMENTATION_SLOT); ++ } ++ ++ function testProxyRejectsProxiableUUIDCall() public { ++ vm.expectRevert(UUPSUpgradeable.UUPSUnauthorizedCallContext.selector); ++ bank.proxiableUUID(); ++ } ++} +diff --git a/test/helpers/BankTestBase.sol b/test/helpers/BankTestBase.sol +new file mode 100644 +index 0000000..9287cc4 +--- /dev/null ++++ b/test/helpers/BankTestBase.sol +@@ -0,0 +1,33 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {Test} from "forge-std/Test.sol"; ++import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; ++ ++import {BankV1} from "../../src/BankV1.sol"; ++import {MockUSDC} from "../../src/MockUSDC.sol"; ++ ++abstract contract BankTestBase is Test { ++ address internal owner; ++ address internal alice; ++ address internal bob; ++ address internal stranger; ++ ++ MockUSDC internal token; ++ address internal proxy; ++ address internal implementation; ++ BankV1 internal bank; ++ ++ function setUp() public virtual { ++ owner = makeAddr("owner"); ++ alice = makeAddr("alice"); ++ bob = makeAddr("bob"); ++ stranger = makeAddr("stranger"); ++ ++ token = new MockUSDC(owner); ++ proxy = ++ Upgrades.deployUUPSProxy("BankV1.sol:BankV1", abi.encodeCall(BankV1.initialize, (address(token), owner))); ++ bank = BankV1(proxy); ++ implementation = Upgrades.getImplementationAddress(proxy); ++ } ++} diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-1f4175b..1f4175b-round1.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-1f4175b..1f4175b-round1.diff new file mode 100644 index 0000000..b4fefe1 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-1f4175b..1f4175b-round1.diff @@ -0,0 +1,7 @@ +# Review package: 1f4175ba729fbbab58538cd3ac02b5459e014c86..1f4175ba729fbbab58538cd3ac02b5459e014c86 + +## Commits + +## Files changed + +## Diff diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-2a21766..6dcbb03.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-2a21766..6dcbb03.diff new file mode 100644 index 0000000..74ac932 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-2a21766..6dcbb03.diff @@ -0,0 +1,145 @@ +# Review package: 2a21766237a9c29970519f7fa00482faecd06fdd..6dcbb03f3c79979416c2f0dd37556539276a4e29 + +## Commits +6dcbb03 test: prove V1 accounting invariants + +## Files changed + test/BankInvariant.t.sol | 43 ++++++++++++++++++++++++ + test/helpers/BankHandler.sol | 79 ++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 122 insertions(+) + +## Diff +diff --git a/test/BankInvariant.t.sol b/test/BankInvariant.t.sol +new file mode 100644 +index 0000000..f699902 +--- /dev/null ++++ b/test/BankInvariant.t.sol +@@ -0,0 +1,43 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {BankTestBase} from "./helpers/BankTestBase.sol"; ++import {BankHandler} from "./helpers/BankHandler.sol"; ++ ++contract BankInvariantTest is BankTestBase { ++ BankHandler internal handler; ++ ++ function setUp() public override { ++ super.setUp(); ++ ++ handler = new BankHandler(token, bank); ++ vm.prank(owner); ++ token.transferOwnership(address(handler)); ++ ++ targetContract(address(handler)); ++ bytes4[] memory selectors = new bytes4[](3); ++ selectors[0] = handler.deposit.selector; ++ selectors[1] = handler.withdraw.selector; ++ selectors[2] = handler.donate.selector; ++ targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); ++ } ++ ++ function invariant_liabilitiesEqualTrackedBalances() public view { ++ uint256 sum; ++ for (uint256 i; i < handler.actorCount(); ++i) { ++ sum += bank.balanceOf(handler.actorAt(i)); ++ } ++ assertEq(sum, bank.totalLiabilities()); ++ } ++ ++ function invariant_reservesCoverLiabilities() public view { ++ assertGe(token.balanceOf(address(bank)), bank.totalLiabilities()); ++ } ++ ++ function invariant_ghostAccountingMatchesChain() public view { ++ assertEq(handler.ghostDeposited() - handler.ghostWithdrawn(), bank.totalLiabilities()); ++ assertEq( ++ handler.ghostDeposited() + handler.ghostDonated() - handler.ghostWithdrawn(), token.balanceOf(address(bank)) ++ ); ++ } ++} +diff --git a/test/helpers/BankHandler.sol b/test/helpers/BankHandler.sol +new file mode 100644 +index 0000000..20ec183 +--- /dev/null ++++ b/test/helpers/BankHandler.sol +@@ -0,0 +1,79 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {Test} from "forge-std/Test.sol"; ++ ++import {BankV1} from "../../src/BankV1.sol"; ++import {MockUSDC} from "../../src/MockUSDC.sol"; ++ ++contract BankHandler is Test { ++ MockUSDC internal immutable token; ++ BankV1 internal immutable bank; ++ ++ address internal immutable actor0; ++ address internal immutable actor1; ++ address internal immutable actor2; ++ address internal immutable actor3; ++ ++ uint256 public ghostDeposited; ++ uint256 public ghostWithdrawn; ++ uint256 public ghostDonated; ++ ++ constructor(MockUSDC token_, BankV1 bank_) { ++ token = token_; ++ bank = bank_; ++ actor0 = address(0x1001); ++ actor1 = address(0x1002); ++ actor2 = address(0x1003); ++ actor3 = address(0x1004); ++ } ++ ++ function deposit(uint256 actorSeed, uint256 amount) external { ++ address actor = actorAt(actorSeed % actorCount()); ++ amount = bound(amount, 1, 10_000e6); ++ token.mint(actor, amount); ++ ++ vm.startPrank(actor); ++ token.approve(address(bank), amount); ++ bank.deposit(amount); ++ vm.stopPrank(); ++ ++ ghostDeposited += amount; ++ } ++ ++ function withdraw(uint256 actorSeed, uint256 amount) external { ++ address actor = actorAt(actorSeed % actorCount()); ++ uint256 balance = bank.balanceOf(actor); ++ if (balance == 0) return; ++ ++ amount = bound(amount, 1, balance); ++ vm.startPrank(actor); ++ bank.withdraw(amount); ++ vm.stopPrank(); ++ ++ ghostWithdrawn += amount; ++ } ++ ++ function donate(uint256 actorSeed, uint256 amount) external { ++ address actor = actorAt(actorSeed % actorCount()); ++ amount = bound(amount, 1, 1_000e6); ++ token.mint(actor, amount); ++ ++ vm.startPrank(actor); ++ token.transfer(address(bank), amount); ++ vm.stopPrank(); ++ ++ ghostDonated += amount; ++ } ++ ++ function actorCount() public pure returns (uint256) { ++ return 4; ++ } ++ ++ function actorAt(uint256 index) public view returns (address) { ++ if (index == 0) return actor0; ++ if (index == 1) return actor1; ++ if (index == 2) return actor2; ++ return actor3; ++ } ++} diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-6dcbb03..6dcbb03-round1.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-6dcbb03..6dcbb03-round1.diff new file mode 100644 index 0000000..1c56cb4 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-6dcbb03..6dcbb03-round1.diff @@ -0,0 +1,7 @@ +# Review package: 6dcbb03f3c79979416c2f0dd37556539276a4e29..6dcbb03f3c79979416c2f0dd37556539276a4e29 + +## Commits + +## Files changed + +## Diff diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-a6f9533..1f4175b.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-a6f9533..1f4175b.diff new file mode 100644 index 0000000..a872f12 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-a6f9533..1f4175b.diff @@ -0,0 +1,101 @@ +# Review package: a6f9533aacd4d6cdbbf9682bfd8482ced065ebf5..1f4175ba729fbbab58538cd3ac02b5459e014c86 + +## Commits +1f4175b feat: add valueless mock USDC + +## Files changed + src/MockUSDC.sol | 18 ++++++++++++++++ + test/MockUSDC.t.sol | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 78 insertions(+) + +## Diff +diff --git a/src/MockUSDC.sol b/src/MockUSDC.sol +new file mode 100644 +index 0000000..96ca297 +--- /dev/null ++++ b/src/MockUSDC.sol +@@ -0,0 +1,18 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; ++import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; ++ ++/// @notice Educational mock token with no monetary value. Never use as real USDC. ++contract MockUSDC is ERC20, Ownable { ++ constructor(address initialOwner) ERC20("Mock USD Coin", "mUSDC") Ownable(initialOwner) {} ++ ++ function decimals() public pure override returns (uint8) { ++ return 6; ++ } ++ ++ function mint(address to, uint256 amount) external onlyOwner { ++ _mint(to, amount); ++ } ++} +diff --git a/test/MockUSDC.t.sol b/test/MockUSDC.t.sol +new file mode 100644 +index 0000000..028c1a0 +--- /dev/null ++++ b/test/MockUSDC.t.sol +@@ -0,0 +1,60 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity 0.8.35; ++ ++import {Test} from "forge-std/Test.sol"; ++import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; ++import {MockUSDC} from "../src/MockUSDC.sol"; ++ ++contract MockUSDCTest is Test { ++ MockUSDC internal token; ++ address internal constant STRANGER = address(0xBEEF); ++ address internal constant RECIPIENT = address(0xCAFE); ++ address internal constant SPENDER = address(0xD00D); ++ ++ function setUp() public { ++ token = new MockUSDC(address(this)); ++ } ++ ++ function testMetadataUsesSixDecimalMockUSDC() public view { ++ assertEq(token.name(), "Mock USD Coin"); ++ assertEq(token.symbol(), "mUSDC"); ++ assertEq(token.decimals(), 6); ++ } ++ ++ function testInitialSupplyIsZero() public view { ++ assertEq(token.totalSupply(), 0); ++ } ++ ++ function testMintMintsToRecipientWhenCalledByOwner() public { ++ token.mint(RECIPIENT, 1_250_000); ++ ++ assertEq(token.totalSupply(), 1_250_000); ++ assertEq(token.balanceOf(RECIPIENT), 1_250_000); ++ } ++ ++ function testMintRevertsWhenCalledByNonOwner() public { ++ vm.prank(STRANGER); ++ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, STRANGER)); ++ token.mint(RECIPIENT, 1); ++ } ++ ++ function testTransferMovesMintedBalance() public { ++ token.mint(address(this), 1_250_000); ++ ++ token.transfer(RECIPIENT, 250_000); ++ ++ assertEq(token.balanceOf(address(this)), 1_000_000); ++ assertEq(token.balanceOf(RECIPIENT), 250_000); ++ } ++ ++ function testApproveSetsAllowanceForSpender() public { ++ token.approve(SPENDER, 750_000); ++ ++ assertEq(token.allowance(address(this), SPENDER), 750_000); ++ } ++ ++ function testConstructorRevertsForZeroInitialOwner() public { ++ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0))); ++ new MockUSDC(address(0)); ++ } ++} diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/review-eece34c..a6f9533.diff b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-eece34c..a6f9533.diff new file mode 100644 index 0000000..629bb7e --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/review-eece34c..a6f9533.diff @@ -0,0 +1,31 @@ +# Review package: eece34c088d52f1650baef34b51a8fa26ec12a00..a6f9533aacd4d6cdbbf9682bfd8482ced065ebf5 + +## Commits +a6f9533 build: approve pinned install scripts + +## Files changed + package.json | 4 ++++ + 1 file changed, 4 insertions(+) + +## Diff +diff --git a/package.json b/package.json +index 1612ecb..dd15769 100644 +--- a/package.json ++++ b/package.json +@@ -3,12 +3,16 @@ + "private": true, + "version": "0.1.0", + "type": "module", + "packageManager": "npm@11.17.0", + "engines": { + "node": ">=24.18.0 <25", + "npm": ">=11.17.0 <12" + }, + "devDependencies": { + "@openzeppelin/upgrades-core": "1.46.0" ++ }, ++ "allowScripts": { ++ "keccak@3.0.4": true, ++ "secp256k1@4.0.5": true + } + } diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-1-brief.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-1-brief.md new file mode 100644 index 0000000..98d9027 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-1-brief.md @@ -0,0 +1,206 @@ +### Task 1: Pin and prove the repository toolchain + +**Files:** +- Modify: `.gitignore` +- Create: `.nvmrc` +- Create: `.env.example` +- Create: `.gitmodules` (generated by `forge install`) +- Create: `foundry.lock` (generated by `forge install`) +- Create: `foundry.toml` +- Create: `remappings.txt` +- Create: `Makefile` +- Create: `package.json` +- Create: `package-lock.json` (generated by npm) +- Create: `tools/check-upgrades-cli.mjs` +- Create: `web/package.json` +- Create: `web/package-lock.json` (generated by npm) +- Create: `web/index.html` +- Create: `web/tsconfig.json` +- Create: `web/tsconfig.app.json` +- Create: `web/tsconfig.node.json` +- Create: `web/vite.config.ts` +- Create: `web/eslint.config.js` +- Create: `web/src/test/toolchain.test.ts` + +**Interfaces:** +- Produces `make doctor`, `make setup`, and the initial verification gate that later tasks extend. +- Produces exact dependency revisions for every later Solidity and web task. +- Consumes no application source. + +- [ ] Verify host tools and record the expected initial limitation: + +```bash +forge --version +anvil --version +node --version +npm --version +make --version +``` + +Expected now: Foundry commands may be missing; Node/npm/make print their installed versions. If Foundry is missing during execution, request permission to install Foundry from its official installer, then pin with `foundryup -i 1.7.1`. Do not silently install system-wide tools. + +- [ ] Extend `.gitignore` with exactly these runtime classes: + +```gitignore +.env +.env.local +.demo/ +cache/ +out/ +broadcast/ +deployments/*.json +deployments/**/*.json +!deployments/*.example.json +node_modules/ +web/node_modules/ +web/dist/ +web/coverage/ +web/public/deployment.json +web/src/generated/*.ts +!.gitkeep +.superpowers/ +``` + +- [ ] Pin Node/npm in `.nvmrc` and create a private root `package.json` with the same `packageManager`/`engines` fields plus exactly one dev dependency: `"@openzeppelin/upgrades-core": "1.46.0"`. Generate and commit the root lockfile with `npm install --save-exact`. This locally satisfies the plugin’s hard-coded `^1.45.0` range. + +- [ ] Pin the browser dependencies in `web/package.json`; the package must be private and use only exact versions: + +```json +{ + "name": "uups-bank-operations-console", + "private": true, + "version": "0.1.0", + "type": "module", + "packageManager": "npm@11.17.0", + "engines": { "node": ">=24.18.0 <25", "npm": ">=11.17.0 <12" }, + "scripts": { + "dev": "vite --host 127.0.0.1", + "lint": "eslint . --max-warnings 0", + "typecheck": "tsc -b --pretty false", + "test": "vitest run", + "build": "tsc -b && vite build" + } +} +``` + +Add exact runtime dependencies `@tanstack/react-query@5.101.4`, `react@19.2.8`, `react-dom@19.2.8`, `viem@2.55.8`, and `wagmi@3.7.5`. Add exact dev dependencies `@eslint/js@10.0.1`, `@testing-library/dom@10.4.1`, `@testing-library/react@16.3.2`, `@types/node@24.10.0`, `@types/react@19.2.14`, `@types/react-dom@19.2.4`, `@vitejs/plugin-react@6.0.4`, `eslint@10.0.1`, `eslint-plugin-react-hooks@7.1.1`, `eslint-plugin-react-refresh@0.5.3`, `globals@17.7.0`, `jsdom@30.0.1`, `typescript@7.0.2`, `typescript-eslint@8.65.0`, `vite@8.2.0`, and `vitest@4.1.10`. If npm rejects one exact revision because the registry changed, verify the official release before changing both this plan and the package file. + +- [ ] Configure Foundry in `foundry.toml`: + +```toml +[profile.default] +src = "src" +test = "test" +script = "script" +out = "out" +libs = ["lib"] +solc_version = "0.8.35" +evm_version = "cancun" +optimizer = true +optimizer_runs = 200 +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] +fs_permissions = [ + { access = "read", path = "out" }, + { access = "read-write", path = "deployments" } +] + +[fuzz] +runs = 512 +seed = "0x5555505342414e4b" + +[invariant] +runs = 128 +depth = 64 +fail_on_revert = true +``` + +- [ ] Install exact Solidity dependencies as Git submodules and write canonical remappings: + +```bash +forge install foundry-rs/forge-std@v1.16.1 +forge install OpenZeppelin/openzeppelin-foundry-upgrades@v0.4.1 +forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v5.6.1 +git submodule update --init --recursive +``` + +```text +forge-std/=lib/forge-std/src/ +openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ +@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +``` + +Do not install a second top-level copy of `openzeppelin-contracts`; the upgradeable submodule’s pinned transitive copy supplies both canonical remappings. + +- [ ] Create `tools/check-upgrades-cli.mjs`. It must assert that the plugin source contains `UPGRADES_CORE = "^1.45.0"`, the root lockfile resolves `@openzeppelin/upgrades-core` to exactly `1.46.0`, and the locally installed package reports `1.46.0`. Then prove the CLI can start with networking disabled: + +```bash +npm ci +node tools/check-upgrades-cli.mjs +npm_config_offline=true npx @openzeppelin/upgrades-core@^1.45.0 validate --help +``` + +Expected: all commands exit `0` without fetching. Do not patch the vendored OpenZeppelin version constant. + +- [ ] Create Vite/React/TypeScript/ESLint/Vitest configuration manually so no unpinned scaffold generator is executed. Configure jsdom, `web/src/test/setup.ts`, strict TypeScript, and React refresh. Run: + +```bash +npm --prefix web install --save-exact +npm --prefix web test +``` + +Expected red: `toolchain.test.ts` cannot import the not-yet-created `src/config/toolchain.ts`. + +- [ ] Create `web/src/config/toolchain.ts` exporting the display labels `Foundry 1.7.1`, `Solidity 0.8.35`, `OpenZeppelin 5.6.1`, and `UUPS`; make the test assert those exact values. + +- [ ] Add an initial `Makefile` with shell safety (`SHELL := /bin/bash`, `.SHELLFLAGS := -euo pipefail -c`) and non-destructive targets: + +```make +.PHONY: doctor setup verify +doctor: + @./tools/doctor.sh +setup: + @git submodule update --init --recursive + @npm ci + @npm --prefix web ci +verify: + @forge fmt --check + @forge clean + @npm_config_offline=true forge build --force + @npm_config_offline=true forge test --force + @npm --prefix web run lint + @npm --prefix web run typecheck + @npm --prefix web test + @npm --prefix web run build +``` + +Create a temporary minimal `tools/doctor.sh` that reports missing commands and exact expected versions without installing anything; Task 9 replaces it with full port/config checks. + +- [ ] Run the green foundation checks: + +```bash +forge fmt --check +forge clean +npm_config_offline=true forge build --force +npm_config_offline=true forge test --force +node tools/check-upgrades-cli.mjs +npm --prefix web run lint +npm --prefix web run typecheck +npm --prefix web test +npm --prefix web run build +``` + +Expected: all exit `0`; the empty Solidity source tree builds and the toolchain test passes. + +- [ ] Commit the foundation: + +```bash +git add .gitignore .nvmrc .env.example .gitmodules foundry.lock foundry.toml remappings.txt package.json package-lock.json Makefile tools/check-upgrades-cli.mjs tools/doctor.sh lib web +git commit -m "build: pin demo toolchains" +``` + +--- + diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-1-report.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-1-report.md new file mode 100644 index 0000000..ae5b090 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-1-report.md @@ -0,0 +1,150 @@ +# Task 1 report: Pin and prove the repository toolchain + +## Implemented + +- Pinned the repository to Node `24.18.0` / npm `11.17.0`, Foundry `1.7.1`, Solidity `0.8.35`, and the required Foundry profiles. +- Added exact root and web dependency manifests and generated lockfiles. The root lock resolves `@openzeppelin/upgrades-core` to `1.46.0`. +- Installed and pinned Git submodules at `forge-std@v1.16.1`, `openzeppelin-foundry-upgrades@v0.4.1`, and `openzeppelin-contracts-upgradeable@v5.6.1`, including canonical remappings. +- Added offline upgrades-core verification, a non-destructive doctor script, Make targets, and a manually configured Vite/React/TypeScript/ESLint/Vitest harness. +- Added the first toolchain-label module and its Vitest coverage. + +## Files changed + +- `.gitignore`, `.nvmrc`, `.env.example`, `.gitmodules`, `foundry.lock`, `foundry.toml`, `remappings.txt`, `Makefile` +- `package.json`, `package-lock.json`, `tools/check-upgrades-cli.mjs`, `tools/doctor.sh` +- `lib/forge-std`, `lib/openzeppelin-foundry-upgrades`, `lib/openzeppelin-contracts-upgradeable` +- `web/package.json`, `web/package-lock.json`, `web/index.html`, `web/tsconfig.json`, `web/tsconfig.app.json`, `web/tsconfig.node.json`, `web/vite.config.ts`, `web/eslint.config.js` +- `web/src/config/toolchain.ts`, `web/src/test/setup.ts`, `web/src/test/toolchain.test.ts` + +## TDD evidence + +The test names the break it catches: a missing or incorrect operations-console toolchain label. Its expected object is hand-written, not derived from the production module. + +### RED + +Command: + +```bash +npm --prefix web test +``` + +Result: exit `1`, with `Failed to resolve import "../config/toolchain" from "src/test/toolchain.test.ts"` and `0 test` execution. This was expected because `web/src/config/toolchain.ts` had not yet been created. + +### GREEN + +Created the minimal `toolchainLabels` export with the four required display values, then ran: + +```bash +npm --prefix web test +``` + +Result: exit `0`; `1 passed` test file and `1 passed` test. + +## Verification results + +Initial host check: Foundry and Anvil were absent; Node was `v26.7.0`, npm `12.0.2`, and Make `4.4.1`. Installed Foundry through the official user-local installer and pinned it with `foundryup -i 1.7.1`; `forge --version` and `anvil --version` now report `1.7.1`. + +The final complete verification command exited `0`: + +```bash +forge fmt --check +forge clean +npm_config_offline=true forge build --force +npm_config_offline=true forge test --force +node tools/check-upgrades-cli.mjs +npm --prefix web run lint +npm --prefix web run typecheck +npm --prefix web test +npm --prefix web run build +make verify +``` + +Observed results: Foundry reported the expected empty-tree `Nothing to compile`; the upgrades-core check reported `@openzeppelin/upgrades-core 1.46.0 is pinned and installed`; lint and typecheck exited `0`; Vitest reported `1 passed`; and Vite built `dist/index.html` successfully. `npm_config_offline=true npx @openzeppelin/upgrades-core@^1.45.0 validate --help` also exited `0` without fetching. + +The restricted filesystem sandbox returns EROFS for `forge clean`; the same required commands exited `0` when run outside that mount restriction. This is an execution-environment limitation, not a repository failure. + +## Self-review + +- Confirmed the three top-level Gitlinks equal the tag revisions in `foundry.lock`; recursive nested submodules were synchronized after `forge install` initially left top-level Gitlinks at their default branch heads. +- Confirmed the upgrades script checks the unmodified vendored `UPGRADES_CORE = "^1.45.0"`, the root lockfile, and the installed package version. +- Confirmed generated output and node modules are ignored, and no generated output is staged. +- Kept TypeScript strict through `tsc -b`; ESLint does not import `typescript-eslint` because that exact package hard-fails under TypeScript 7.0. + +## Concerns + +1. The task-mandated `typescript@7.0.2` and `typescript-eslint@8.65.0` are upstream-incompatible. The latter declares TypeScript `<6.1.0` and throws when loaded under TypeScript 7. Official TypeScript 7 documentation states that TypeScript 7 ships without the compiler API and advises a side-by-side `@typescript/typescript6` alias for tools such as typescript-eslint. Changing to that official arrangement would violate the brief’s required direct `typescript@7.0.2` pin, so it was not done. The ESLint config therefore uses ESLint core plus React rules, while strict TypeScript checking remains green through `tsc -b`. +2. The same peer conflict makes a clean `npm --prefix web ci` (including with pinned npm `11.17.0`) fail unless legacy peer resolution is supplied. The committed lockfile was generated with `npm --prefix web install --save-exact --legacy-peer-deps`; exact versions and the offline verification gate were not weakened. Consequently `make setup` inherits this known npm peer-resolution limitation on a clean install. +3. The current host is outside the pinned Node/npm engines (`v26.7.0` / `12.0.2`), so npm emits engine warnings during installation. The repository records the requested Node/npm pins; final lint/typecheck/test/build all pass on the available host. + +## Compatibility resolution + +The first two concerns above are resolved. Microsoft’s official TypeScript 7 announcement explains that TypeScript 7.0 ships without the compiler API and directs tooling such as typescript-eslint to a side-by-side TypeScript 6 API package. The package now uses only range-free aliases: + +- `typescript`: `npm:@typescript/typescript6@6.0.2`, which supplies the API imported by `typescript-eslint@8.65.0`. +- `@typescript/native`: `npm:typescript@7.0.2`, which supplies the production compiler. + +`typecheck` and `build` invoke `node ./node_modules/@typescript/native/bin/tsc` explicitly, so they cannot select an ambiguous binary. ESLint now imports and applies `typescript-eslint` again. The Tech Stack and Task 1 dependency wording in the implementation plan were updated under its stated compatibility-rejection condition. + +### Resolution RED + +Before the alias change, the clean pinned-runtime command failed as expected: + +```bash +npx --yes npm@11.17.0 --prefix web ci +``` + +Result: exit `1` with `ERESOLVE`; `typescript-eslint@8.65.0` required `typescript >=4.8.4 <6.1.0` while the direct package was TypeScript `7.0.2`. + +### Resolution GREEN + +After the alias change, all of these exited `0` without `legacy-peer-deps`: + +```bash +npx --yes npm@11.17.0 --prefix web ci +node -p "require('./web/node_modules/typescript/package.json').version" +node ./web/node_modules/@typescript/native/bin/tsc --version +``` + +Observed output: TypeScript API alias `6.0.2` and compiler `Version 7.0.2`. With npm `11.17.0` placed first on `PATH`, both `make setup` and `make verify` exited `0`; the latter passed ESLint with typescript-eslint enabled, strict typecheck, Vitest (`1 passed`), and Vite build. The final standalone Foundry/offline CLI/web gate also exited `0`. + +## Fix Round 1: exact Node runtime proof + +### Runtime installation and version proof + +Downloaded the official Node `v24.18.0` Linux archive to `/tmp`, verified it against Node’s published `SHASUMS256.txt`, and installed it outside repository state at `/tmp/codius-node-v24.18.0`. Its bundled npm was `11.16.0`, so npm `11.17.0` was installed exactly into that same temporary prefix. With that prefix first on `PATH`, the proof output was: + +```text +node: v24.18.0 +npm: 11.17.0 +forge: forge Version: 1.7.1 +anvil: anvil Version: 1.7.1 +``` + +### Warning investigation and correction + +The first clean exact-runtime setup emitted npm 11.17’s `allow-scripts` advisory for `keccak@3.0.4` and `secp256k1@4.0.5`. Both packages declare the reviewed install command `node-gyp-build || exit 0`. npm 11.17’s `approve-scripts` documentation identifies `package.json` `allowScripts` as the intended project policy, so the manifest now records exact-version approvals for those two packages. No dependency version or install mode changed. + +### Covering commands and results + +Covering test file: `web/src/test/toolchain.test.ts` (Vitest: `1 passed`). + +With `PATH=/tmp/codius-node-v24.18.0/bin:/home/golem/.foundry/bin:$PATH`, these all exited `0` from a clean install: + +```bash +make doctor +make setup +forge fmt --check +forge clean +npm_config_offline=true forge build --force +npm_config_offline=true forge test --force +node tools/check-upgrades-cli.mjs +npm_config_offline=true npx @openzeppelin/upgrades-core@^1.45.0 validate --help +npm --prefix web run lint +npm --prefix web run typecheck +npm --prefix web test +npm --prefix web run build +node ./web/node_modules/@typescript/native/bin/tsc --version +make verify +``` + +`make setup` completed without npm warnings on the exact Node/npm pair. The explicit compiler command printed `Version 7.0.2`; the offline CLI printed its help without fetching; Foundry reported the expected zero-source `Nothing to compile`; lint/typecheck passed; and the Vite build succeeded. `forge fmt --check` retains Foundry’s informational empty-tree `Nothing to format` notice because this foundation task intentionally has no Solidity sources. diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-2-brief.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-2-brief.md new file mode 100644 index 0000000..3f89891 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-2-brief.md @@ -0,0 +1,52 @@ +### Task 2: Build the valueless six-decimal mock asset with TDD + +**Files:** +- Create: `src/MockUSDC.sol` +- Create: `test/MockUSDC.t.sol` + +**Interfaces:** +- Produces `MockUSDC.mint(address,uint256)`, `decimals() == 6`, and standard ERC-20 behavior. +- Consumed by the bank tests, deployment scripts, and dashboard ABI export. + +- [ ] Write `test/MockUSDC.t.sol` first. Cover the exact name/symbol/decimals, owner-only mint, successful mint, transfer/approve behavior inherited from ERC-20, and zero initial supply. Use `vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, stranger))` for non-owner minting. + +- [ ] Run the focused test and observe red: + +```bash +forge test --match-path test/MockUSDC.t.sol -vvv --force +``` + +Expected red: `src/MockUSDC.sol` is missing. + +- [ ] Implement only the tested contract: + +```solidity +/// @notice Educational mock token with no monetary value. Never use as real USDC. +contract MockUSDC is ERC20, Ownable { + constructor(address initialOwner) ERC20("Mock USD Coin", "mUSDC") Ownable(initialOwner) {} + + function decimals() public pure override returns (uint8) { return 6; } + function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); } +} +``` + +Reject a zero `initialOwner` through OpenZeppelin’s `OwnableInvalidOwner` behavior. Do not add faucet, burn, permit, blacklist, proxy, or bank-specific logic. + +- [ ] Run red-to-green verification: + +```bash +forge fmt +forge test --match-path test/MockUSDC.t.sol -vvv --force +``` + +Expected: all mock-token tests pass. + +- [ ] Commit: + +```bash +git add src/MockUSDC.sol test/MockUSDC.t.sol +git commit -m "feat: add valueless mock USDC" +``` + +--- + diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-2-report.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-2-report.md new file mode 100644 index 0000000..d4b785f --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-2-report.md @@ -0,0 +1,133 @@ +# Task 2 Report: Valueless Six-Decimal Mock Asset + +## Implementation + +Added `MockUSDC`, an educational ERC-20 mock with no monetary value. It uses OpenZeppelin `ERC20` and `Ownable`, initializes the token as `Mock USD Coin` (`mUSDC`), overrides `decimals()` to return `6`, and permits `mint(address,uint256)` only to the configured owner. OpenZeppelin rejects `address(0)` as the initial owner via `OwnableInvalidOwner`. + +## Changed Files + +- `src/MockUSDC.sol` (new): minimal six-decimal owner-mintable ERC-20 mock. +- `test/MockUSDC.t.sol` (new): focused tests for metadata, zero supply, authorized/unauthorized minting, transfer, approval, and zero-owner rejection. + +## TDD Evidence + +Before writing production code, the test suite was added. The production change that each test guards was identified as a missing or incorrect ERC-20 observable behavior: metadata/decimals, initial supply, owner authorization and mint balances, transfer balance movement, approval allowance, or constructor owner validation. + +### RED + +Command: + +```bash +PATH=/home/golem/.foundry/bin:$PATH forge test --match-path test/MockUSDC.t.sol -vvv --force +``` + +Relevant output: + +```text +Error (6275): Source "src/MockUSDC.sol" not found: File not found. + --> test/MockUSDC.t.sol:6:1: +Error: Compilation failed +``` + +This was the expected RED result: the test imports the intentionally absent `src/MockUSDC.sol`; it failed because the requested feature did not yet exist, not because of a test typo or unrelated failure. + +### GREEN + +Commands: + +```bash +PATH=/home/golem/.foundry/bin:$PATH forge fmt +PATH=/home/golem/.foundry/bin:$PATH forge test --match-path test/MockUSDC.t.sol -vvv --force +``` + +Relevant output: + +```text +Ran 7 tests for test/MockUSDC.t.sol:MockUSDCTest +Suite result: ok. 7 passed; 0 failed; 0 skipped +``` + +## Full Relevant Suite + +Command: + +```bash +PATH=/home/golem/.foundry/bin:$PATH forge test -vvv +``` + +Result: + +```text +Ran 1 test suite: 7 tests passed, 0 failed, 0 skipped (7 total tests) +``` + +`git diff --check` also completed without reporting whitespace errors. + +## Self-Review + +- Implementation contains only the briefed `ERC20`, `Ownable`, `decimals`, and owner-only `mint` behavior; no faucet, burn, permit, blacklist, proxy, or bank-specific logic was added. +- Tests exercise the deployed contract's observable state and OpenZeppelin's real authorization behavior; no mocks were used. +- The non-owner case uses the required exact `Ownable.OwnableUnauthorizedAccount` selector and `stranger` address. +- Mutations such as changing metadata/decimals, allowing arbitrary minting, omitting minting, misrouting transfers, failing to set allowance, or accepting a zero owner are covered by at least one test. + +## Concerns + +None. The report itself is intentionally left uncommitted because the task required committing only `src/MockUSDC.sol` and `test/MockUSDC.t.sol`. + +## Fix Round 1: Literal Command Evidence + +Foundry was placed on `PATH` before each command session with a separate shell environment setup step. The commands below were then executed literally, with no inline `PATH` prefix. + +### Isolated RED Reproduction + +An isolated temporary filesystem copy of the complete worktree (including the already-installed pinned dependencies) was created under `/tmp`, and only `src/MockUSDC.sol` was removed from that copy. This keeps the real committed worktree untouched while preserving the same test file and dependency graph. + +Covering test file: `test/MockUSDC.t.sol` + +Literal command: + +```bash +forge test --match-path test/MockUSDC.t.sol -vvv --force +``` + +Relevant output: + +```text +Error (6275): Source "src/MockUSDC.sol" not found: File not found. + --> test/MockUSDC.t.sol:6:1: +Error: Compilation failed +``` + +This RED result is correct because the isolated state retains the new test but deliberately lacks the production contract it imports. The failure is therefore the expected feature-missing failure, rather than a dependency, compiler, or test error. + +### Real-Worktree GREEN + +Literal commands: + +```bash +forge fmt +forge test --match-path test/MockUSDC.t.sol -vvv --force +``` + +Relevant output: + +```text +Ran 7 tests for test/MockUSDC.t.sol:MockUSDCTest +Suite result: ok. 7 passed; 0 failed; 0 skipped +``` + +### Full Relevant Suite + +Literal command: + +```bash +forge test -vvv +``` + +Result: + +```text +Ran 1 test suite: 7 tests passed, 0 failed, 0 skipped (7 total tests) +``` + +No tracked implementation or test file changed during this correction, so no tracked commit was needed. Commit `1f4175b feat: add valueless mock USDC` remains the sole task commit. diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-brief.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-brief.md new file mode 100644 index 0000000..0a20826 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-brief.md @@ -0,0 +1,99 @@ +### Task 3: Establish the V1 proxy, initialization, views, and administration + +**Files:** +- Create: `src/BankV1.sol` +- Create: `test/helpers/BankTestBase.sol` +- Create: `test/BankV1Admin.t.sol` + +**Interfaces:** +- Produces `initialize(address,address)`, `asset()`, `balanceOf(address)`, `totalLiabilities()`, `contractVersion()`, `pause()`, `unpause()`, and owner-authorized UUPS upgrades. +- Consumes `MockUSDC` and OpenZeppelin `Upgrades.deployUUPSProxy`. +- Freezes V1 application storage as `_asset`, `_balances`, `_totalLiabilities`, then `uint256[47] __gap`. + +- [ ] Create `BankTestBase.sol` with deterministic `owner`, `alice`, `bob`, and `stranger` addresses from `makeAddr`; deploy `MockUSDC`; deploy the proxy with: + +```solidity +proxy = Upgrades.deployUUPSProxy( + "BankV1.sol:BankV1", + abi.encodeCall(BankV1.initialize, (address(token), owner)) +); +bank = BankV1(proxy); +``` + +All bank calls in tests target `proxy`; retain `implementation = Upgrades.getImplementationAddress(proxy)` only for assertions. + +- [ ] Write failing admin tests for: + + - initialized asset, owner, unpaused state, zero liabilities, and version `1`; + - initialization with zero asset; + - initialization with zero owner through `OwnableInvalidOwner`; + - proxy double initialization; + - direct implementation initialization; + - only owner can pause/unpause; + - views still work while paused; + - `_authorizeUpgrade` rejects a non-owner through `upgradeToAndCall`; + - implementation `proxiableUUID()` is available directly while the proxy-context call reverts. + +- [ ] Run the focused suite and observe red: + +```bash +npm_config_offline=true forge test --match-path test/BankV1Admin.t.sol -vvv --force +``` + +Expected red: `BankV1` is missing. + +- [ ] Implement the minimum V1 skeleton with these imports and inheritance order: + +```solidity +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; + +contract BankV1 is + Initializable, + UUPSUpgradeable, + OwnableUpgradeable, + PausableUpgradeable, + ReentrancyGuard +{ + IERC20 internal _asset; + mapping(address account => uint256 balance) internal _balances; + uint256 internal _totalLiabilities; + uint256[47] private __gap; +} +``` + +Use `error InvalidAsset(address asset);`. The initializer checks the asset before calling only `__Ownable_init(initialOwner)` and `__Pausable_init()`. `Initializable`, `UUPSUpgradeable`, and `ReentrancyGuard` are stateless/shared in pinned OpenZeppelin 5.6.1 and have no initializer calls. + +- [ ] Add the only permitted validator annotation and no other bypass: + +```solidity +/// @custom:oz-upgrades-unsafe-allow constructor +constructor() { + _disableInitializers(); +} +``` + +- [ ] Add `pause`/`unpause` with `onlyOwner`, simple views, `contractVersion() public pure virtual returns (uint256)`, and `_authorizeUpgrade(address) internal override onlyOwner {}`. Do not add deposits or withdrawals yet. + +- [ ] Run green verification and inspect storage: + +```bash +forge fmt +npm_config_offline=true forge test --match-path test/BankV1Admin.t.sol -vvv --force +forge inspect BankV1 storage-layout +``` + +Expected: tests pass and the application fields appear in the frozen order. OpenZeppelin namespaced/stateless internals must not be mistaken for permission to reorder application fields. + +- [ ] Commit: + +```bash +git add src/BankV1.sol test/helpers/BankTestBase.sol test/BankV1Admin.t.sol +git commit -m "feat: establish UUPS bank V1" +``` + +--- + diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-report.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-report.md new file mode 100644 index 0000000..a85dab2 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-report.md @@ -0,0 +1,132 @@ +# Task 3 Report: V1 proxy, initialization, views, and administration + +## Status + +Complete. BankV1 now provides the initialized UUPS proxy shell, frozen V1 application storage, administrative pause controls, read-only accounting views, version reporting, and owner-gated upgrade authorization. The final implementation uses OpenZeppelin 5.6.1 `ReentrancyGuardTransient`, as explicitly selected by the user after the pinned validator incompatibility investigation described below. + +## Implementation + +- Added `BankV1` with inheritance in the required order: `Initializable`, `UUPSUpgradeable`, `OwnableUpgradeable`, `PausableUpgradeable`, then the approved constructor-free `ReentrancyGuardTransient`. +- Kept the V1 application fields in the frozen order: `_asset`, `_balances`, `_totalLiabilities`, then `uint256[47] __gap`. +- Disabled initialization on the implementation constructor with the sole validator annotation `@custom:oz-upgrades-unsafe-allow constructor`. +- Added `initialize(address,address)`, checking the asset before invoking only `__Ownable_init(initialOwner)` and `__Pausable_init()`. +- Added owner-only `pause()` and `unpause()`. +- Added `asset()`, `balanceOf(address)`, `totalLiabilities()`, and virtual pure `contractVersion()` returning literal `1`. +- Added owner-gated `_authorizeUpgrade(address)`. +- Added a reusable proxy fixture with deterministic `owner`, `alice`, `bob`, and `stranger` addresses, a `MockUSDC`, `Upgrades.deployUUPSProxy`, proxy-bound `bank`, and implementation address retained only for assertions. +- Added 12 behavior tests covering successful initialization, validation order, disabled/double initialization, ownership and pause administration, paused views, upgrade authorization, and direct-versus-proxy UUPS context. +- Updated the implementation plan's global, Task 3, scanner, and source-guard references to record the explicitly approved transient-guard design and restore the single ordinary constructor annotation. + +## Files + +- `src/BankV1.sol` — V1 UUPS implementation shell. +- `test/helpers/BankTestBase.sol` — reusable proxy deployment fixture. +- `test/BankV1Admin.t.sol` — initialization, administration, views, and UUPS-context tests. +- `docs/superpowers/plans/2026-08-17-uups-bank-demo.md` — narrowly authorized compatibility/design update. +- `.superpowers/sdd/2026-08-17-uups-bank-demo/task-3-report.md` — this ignored task report; not part of the commit. + +## TDD RED evidence + +The required tool binaries were placed on `PATH` in a separate persistent-shell setup. The literal RED command was then issued exactly as briefed: + +```bash +npm_config_offline=true forge test --match-path test/BankV1Admin.t.sol -vvv --force +``` + +Relevant output: + +```text +Compiler run failed: +Error (6275): Source "src/BankV1.sol" not found: File not found. + --> test/BankV1Admin.t.sol:9:1 +Error (6275): Source "src/BankV1.sol" not found: File not found. + --> test/helpers/BankTestBase.sol:7:1 +Error: Compilation failed +``` + +This was the expected RED: the tests and fixture referenced the required production API before `BankV1` existed, and failed specifically because the feature was missing. + +## Validator incompatibility investigation and rulings + +The first literal GREEN attempt used the brief's original non-upgradeable `ReentrancyGuard` and sole local `@custom:oz-upgrades-unsafe-allow constructor`. Solidity compiled, but Foundry Upgrades validation rejected the inherited dependency: + +```text +Upgrade safety validation failed: +lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol:58: +Contract `ReentrancyGuard` has a constructor +``` + +Investigation established: + +- Pinned OpenZeppelin Contracts is 5.6.1 and marks `ReentrancyGuard` `@custom:stateless`, but that guard still has a constructor that initializes its namespaced guard slot. +- Pinned upgrades-core is 1.46.0 and contains no support for the `@custom:stateless` annotation. +- upgrades-core aggregates constructor errors from inherited contracts. + +The first user ruling approved replacing the local annotation with `@custom:oz-upgrades-unsafe-allow-reachable constructor`. The exact change was made and the literal focused command rerun, but validation failed identically. Source-level tracing then established that upgrades-core 1.46.0 uses `skipCheckReachable` only for opcode traversal (`delegatecall`/`selfdestruct`); `getConstructorErrors` checks annotations lexically on each constructor node, so an annotation on `BankV1` cannot suppress an inherited constructor finding. + +No `Options.unsafeAllow`, exclude, skip, `UnsafeUpgrades`, dependency edit, or other workaround was applied. After that evidence, the user's final ruling selected OpenZeppelin 5.6.1 `ReentrancyGuardTransient` and restored the ordinary sole constructor annotation. This guard has no constructor or persistent application storage and is appropriate for this project's explicitly Cancun-targeted Anvil and Base Sepolia networks, where EIP-1153 is available. + +One subsequent test-harness issue was also corrected: `vm.expectRevert` placed before `Upgrades.deployUUPSProxy` intercepted the library's preliminary implementation deployment. The invalid-initializer tests now construct a real `ERC1967Proxy` against the already validated implementation, making the next external operation the intended initializer delegatecall and preserving exact custom-error assertions. + +## GREEN and verification evidence + +The brief's literal GREEN sequence was run in the same shell after the separate PATH setup: + +```bash +forge fmt +npm_config_offline=true forge test --match-path test/BankV1Admin.t.sol -vvv --force +forge inspect BankV1 storage-layout +``` + +Final focused result: + +```text +Ran 12 tests for test/BankV1Admin.t.sol:BankV1AdminTest +Suite result: ok. 12 passed; 0 failed; 0 skipped +``` + +Storage inspection result: + +```text +_asset IERC20 slot 0 offset 0 +_balances mapping(address => uint256) slot 1 offset 0 +_totalLiabilities uint256 slot 2 offset 0 +__gap uint256[47] slot 3 offset 0 (1504 bytes) +``` + +The final full validation-bearing suite was run offline: + +```bash +npm_config_offline=true forge test -vvv --force +``` + +Result: + +```text +Ran 2 test suites: 19 tests passed, 0 failed, 0 skipped +``` + +Both focused and full commands compiled successfully with only two existing dependency warnings in `openzeppelin-foundry-upgrades` about functions whose mutability could be `pure`; no project warning or error was emitted. + +## Storage and upgrade safety notes + +- Application storage is exactly `_asset` at slot 0, `_balances` at slot 1, `_totalLiabilities` at slot 2, and the 47-word gap beginning at slot 3. Later versions must not reorder, remove, or change the type of these fields. +- OpenZeppelin ownership and pause state use ERC-7201 namespaced storage. UUPS, Initializable, and the approved transient reentrancy guard do not consume ordinary application slots; this does not permit reordering the frozen application fields. +- Every ordinary bank call in the fixture and tests targets `BankV1(proxy)`. The implementation address is used only to assert disabled initialization and direct `proxiableUUID()` behavior, as required. +- `proxiableUUID()` returns the literal ERC-1967 implementation slot on the implementation and rejects proxy-context calls with `UUPSUnauthorizedCallContext`. +- `_authorizeUpgrade` is covered through the public proxy `upgradeToAndCall` boundary and rejects a non-owner with `OwnableUnauthorizedAccount`. +- The transient guard restricts supported execution environments to EIP-1153-capable networks. That is an explicit design constraint, not a hidden fallback. + +## Self-review + +- Reviewed all four committed diffs and ran `git diff --check`; no whitespace errors were found. +- Confirmed the fixture uses the exact `Upgrades.deployUUPSProxy("BankV1.sol:BankV1", abi.encodeCall(...))` form and binds all application behavior to the proxy. +- Confirmed only one source validator annotation exists and no `unsafeSkip`, `UnsafeUpgrades`, Options `unsafeAllow`, exclude, reachable annotation, or dependency edit exists in Task 3 code/tests. +- Strengthened the zero-asset test to pass both zero asset and zero owner, so it proves the mandated asset-check ordering; a mutation that invokes ownable initialization first now fails that test. +- Mentally checked realistic mutations: wrong/missing asset assignment, wrong version, missing owner initialization, missing pause authorization/state change, pausing views, enabled reinitialization, implementation initialization, missing upgrade authorization, or broken UUPS call context each fail at least one test. +- No deposit or withdrawal behavior was added. + +## Concerns + +- `ReentrancyGuardTransient` requires EIP-1153/Cancun. The approved project targets (Anvil with `evm_version = "cancun"` and Base Sepolia) satisfy this, but deploying this implementation on a pre-Cancun EVM is unsupported. +- Existing dependency-only compiler mutability warnings remain; they do not originate from Task 3 files. diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-4-brief.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-4-brief.md new file mode 100644 index 0000000..2f604eb --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-4-brief.md @@ -0,0 +1,96 @@ +### Task 4: Implement V1 custody accounting and adversarial unit tests + +**Files:** +- Modify: `src/BankV1.sol` +- Create: `test/BankV1.t.sol` +- Create: `test/mocks/FeeOnTransferToken.sol` +- Create: `test/mocks/ReentrantToken.sol` + +**Interfaces:** +- Produces `deposit(uint256)` and `withdraw(uint256)` plus `Deposited`, `Withdrawn`, `ZeroAmount`, `InsufficientBalance`, and `UnexpectedAssetDelta`. +- Consumes only a standard non-rebasing ERC-20 through `IERC20`/`SafeERC20`. +- Preserves `reserves >= totalLiabilities`; direct token transfers may create surplus. + +- [ ] Write event and error declarations into the test expectations before production code: + +```solidity +event Deposited(address indexed account, uint256 amount); +event Withdrawn(address indexed account, uint256 amount); + +error ZeroAmount(); +error InsufficientBalance(address account, uint256 available, uint256 requested); +error UnexpectedAssetDelta(uint256 expected, uint256 actual); +``` + +- [ ] Write failing deposit tests for exact balance/liability/reserve deltas, event emission, zero amount, paused state, inadequate allowance, inadequate wallet balance, and two independent customers. Use six-decimal constants (`1_000e6`) so assertions read like the demo. + +- [ ] Add `FeeOnTransferToken` whose `transferFrom` delivers `99%` of the requested amount. Assert a deposit reverts with `UnexpectedAssetDelta(requested, received)` and that the ERC-20 transfer, bank balance, and liabilities are all rolled back. + +- [ ] Add `ReentrantToken` that attempts a nested `bank.deposit` during `transferFrom`. Assert the nested call receives `ReentrancyGuardReentrantCall` and the outer call either completes once or reverts atomically according to the mock’s configured propagation mode; no double credit is permitted. + +- [ ] Write failing withdrawal tests for exact deltas, event emission, zero amount, paused state, insufficient internal balance (including available/requested values), checks-effects-interactions under a transfer callback, and one account’s withdrawal leaving another account unchanged. + +- [ ] Write the surplus test: deposit 100 mUSDC, transfer 25 mUSDC directly to the proxy, assert reserves `125e6`, liabilities `100e6`, and a normal 100 mUSDC withdrawal succeeds while the 25 mUSDC surplus remains. Assert there is no owner sweep/rescue behavior by keeping such a function out of the interface. + +- [ ] Run red tests: + +```bash +npm_config_offline=true forge test --match-path test/BankV1.t.sol -vvv --force +``` + +Expected red: `deposit`/`withdraw` and their errors/events are absent. + +- [ ] Implement `deposit` with exact received-amount validation and state credit only after a successful transfer: + +```solidity +function deposit(uint256 amount) external whenNotPaused nonReentrant { + if (amount == 0) revert ZeroAmount(); + + uint256 reservesBefore = _asset.balanceOf(address(this)); + _asset.safeTransferFrom(msg.sender, address(this), amount); + uint256 reservesAfter = _asset.balanceOf(address(this)); + uint256 received = reservesAfter >= reservesBefore ? reservesAfter - reservesBefore : 0; + if (received != amount) revert UnexpectedAssetDelta(amount, received); + + _balances[msg.sender] += amount; + _totalLiabilities += amount; + emit Deposited(msg.sender, amount); +} +``` + +- [ ] Implement `withdraw` with checks-effects-interactions: + +```solidity +function withdraw(uint256 amount) external whenNotPaused nonReentrant { + if (amount == 0) revert ZeroAmount(); + uint256 available = _balances[msg.sender]; + if (amount > available) revert InsufficientBalance(msg.sender, available, amount); + + _balances[msg.sender] = available - amount; + _totalLiabilities -= amount; + _asset.safeTransfer(msg.sender, amount); + emit Withdrawn(msg.sender, amount); +} +``` + +- [ ] Add fuzz tests in `BankV1.t.sol`: bound deposit to `[1, 1_000_000e6]`; bound withdrawal to `[1, deposited]`; prove exact reserve/liability/customer deltas and that over-withdraw always reverts. Keep a fixed seed in `foundry.toml` for presentation reproducibility while printing Foundry’s replay seed on failure. + +- [ ] Run focused and aggregate green tests: + +```bash +forge fmt +npm_config_offline=true forge test --match-path test/BankV1.t.sol -vvv --force +npm_config_offline=true forge test --match-path 'test/BankV1*.t.sol' --force +``` + +Expected: all V1 tests pass with validated proxy deployment enabled. + +- [ ] Commit: + +```bash +git add src/BankV1.sol test/BankV1.t.sol test/mocks/FeeOnTransferToken.sol test/mocks/ReentrantToken.sol +git commit -m "feat: add V1 custody accounting" +``` + +--- + diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-4-report.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-4-report.md new file mode 100644 index 0000000..5def323 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-4-report.md @@ -0,0 +1,133 @@ +# Task 4 Report: V1 Custody Accounting and Adversarial Unit Tests + +## Status + +Complete. `BankV1` now supports exact-accounting deposits and CEI withdrawals behind the existing UUPS proxy. The focused custody suite, aggregate V1 suite, and full offline suite pass. + +## Implementation + +- Added `Deposited` and `Withdrawn` events and the exact `ZeroAmount`, `InsufficientBalance`, and `UnexpectedAssetDelta` errors. +- Added `deposit(uint256)` guarded by `whenNotPaused` and `nonReentrant`. It measures proxy reserves before and after `SafeERC20.safeTransferFrom`, rejects any non-exact received amount, and credits customer balance/liabilities only after the transfer and delta validation succeed. +- Added `withdraw(uint256)` guarded by `whenNotPaused` and `nonReentrant`. It validates zero/available balance, debits customer balance and liabilities before the external `SafeERC20.safeTransfer`, and emits the withdrawal event. +- Preserved application storage exactly: `_asset` slot 0, `_balances` slot 1, `_totalLiabilities` slot 2, and `uint256[47] __gap` slot 3. +- Kept all application behavior proxy-bound and added no sweep/rescue interface. + +## Changed Files + +- `src/BankV1.sol` — custody API, errors/events, SafeERC20 accounting. +- `test/BankV1.t.sol` — 18 deterministic and 3 fuzz custody tests. +- `test/mocks/FeeOnTransferToken.sol` — six-decimal ERC-20 whose `transferFrom` delivers 99% and burns the remainder. +- `test/mocks/ReentrantToken.sol` — six-decimal ERC-20 with configurable deposit/withdraw callbacks, revert propagation, nested-call result/selector recording, and callback-time accounting observations. + +## TDD RED + +Tool paths were set separately in a dedicated shell: + +```bash +export PATH=/tmp/codius-node-v24.18.0/bin:/home/golem/.foundry/bin:$PATH +``` + +The required RED command was run verbatim: + +```bash +npm_config_offline=true forge test --match-path test/BankV1.t.sol -vvv --force +``` + +The first invocation found a Solidity tuple-declaration syntax error in the new `ReentrantToken` test double. Only that test-double syntax was corrected; production remained unchanged. The command was rerun verbatim and produced the valid expected RED: + +```text +Compiler run failed: +Error (9582): Member "deposit" not found or not visible after argument-dependent lookup in contract BankV1. + --> test/BankV1.t.sol:28:9: +28 | bank.deposit(100e6); + | ^^^^^^^^^^^^ +Error: Compilation failed +``` + +This RED was expected because the custody tests called the required proxy-bound `deposit`/`withdraw` API before those functions existed. It therefore failed for the missing production behavior rather than a malformed assertion or environment problem. + +## GREEN Verification + +All upgrade-validation-bearing commands were kept offline. + +```bash +forge fmt +``` + +Output: completed successfully and formatted `test/BankV1.t.sol` on the initial run; the final run completed with no further formatting changes. + +```bash +npm_config_offline=true forge test --match-path test/BankV1.t.sol -vvv --force +``` + +Final output summary: + +```text +Ran 21 tests for test/BankV1.t.sol:BankV1CustodyTest +Suite result: ok. 21 passed; 0 failed; 0 skipped +Fuzz tests: 512 runs each +``` + +```bash +npm_config_offline=true forge test --match-path 'test/BankV1*.t.sol' --force +``` + +Final output summary: + +```text +BankV1AdminTest: 12 passed; 0 failed; 0 skipped +BankV1CustodyTest: 21 passed; 0 failed; 0 skipped +Ran 2 test suites: 33 passed, 0 failed, 0 skipped +``` + +Full-suite command: + +```bash +npm_config_offline=true forge test --force +``` + +Final output summary: + +```text +BankV1AdminTest: 12 passed +MockUSDCTest: 7 passed +BankV1CustodyTest: 21 passed +Ran 3 test suites: 40 passed, 0 failed, 0 skipped +``` + +Solc emitted only two pre-existing dependency warnings in `openzeppelin-foundry-upgrades` about functions whose mutability could be `pure`; there were no project warnings or test failures. + +Storage inspection: + +```bash +forge inspect src/BankV1.sol:BankV1 storage-layout +``` + +Output confirmed `_asset` at slot 0, `_balances` at slot 1, `_totalLiabilities` at slot 2, and `__gap` at slot 3. + +## Accounting and Adversarial Coverage + +- Exact wallet, reserve, customer balance, and total-liability deltas for deposit and withdrawal. +- Indexed event emission for each custody operation. +- Zero amount and paused-state rejection for both operations. +- Exact ERC-6093 inadequate allowance and wallet-balance failures, with empty bank accounting after revert. +- Independent deposits for Alice and Bob and a withdrawal that leaves the other customer's balance unchanged. +- Fee-on-transfer deposit rejection with `UnexpectedAssetDelta(100e6, 99e6)` and atomic rollback of sender balance, bank reserves, customer credit, and liabilities. +- Deposit callback observes zero customer credit/liabilities before transfer completion, attempts nested deposit, and records `ReentrancyGuardReentrantCall`; swallowing the nested revert credits exactly once, while configured propagation atomically reverts the outer deposit. +- Withdrawal callback observes already-debited customer balance/liabilities, attempts nested withdrawal, records `ReentrancyGuardReentrantCall`, and cannot double debit. +- Direct-transfer surplus: 100 mUSDC liability plus 25 mUSDC direct transfer yields 125 mUSDC reserves; withdrawing the full 100 mUSDC customer balance leaves the 25 mUSDC surplus. +- Deterministic fuzzing uses the existing fixed seed, bounds deposits to `[1, 1_000_000e6]`, bounds withdrawals to `[1, deposited]`, proves exact deltas, and proves every bounded over-withdraw reverts with exact available/requested values. + +## Self-Review + +- `git diff --check` passed. +- Diff inspection confirmed no persistent storage changes and no owner sweep/rescue behavior. +- Mutation review: tests fail for missing credit/debit/liability changes, incorrect transfer amount or event, removed zero/pause/available checks, fee-token over-credit, credit-before-deposit-transfer, interaction-before-withdraw-effects, absent reentrancy guard, customer cross-account corruption, or surplus consumption. +- Independent read-only review reported no Critical, Important, or Minor findings and assessed the change ready to merge. The reviewer independently saw 21/21 focused and 33/33 aggregate tests pass and confirmed formatting/diff checks. +- Only the four task-listed source/test files are staged for the commit; this report is intentionally outside that scoped commit. + +## Concerns + +- The bank intentionally supports only standard non-rebasing ERC-20 assets. Outbound transfer-fee/rebasing behavior is outside V1's stated contract. +- `ReentrancyGuardTransient` requires Cancun/EIP-1153, matching the project's configured target. +- The two compiler warnings originate in the pinned `openzeppelin-foundry-upgrades` dependency and are unrelated to this change. diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-5-brief.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-5-brief.md new file mode 100644 index 0000000..186377a --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-5-brief.md @@ -0,0 +1,72 @@ +### Task 5: Prove V1 ledger conservation and solvency with stateful invariants + +**Files:** +- Create: `test/helpers/BankHandler.sol` +- Create: `test/BankInvariant.t.sol` + +**Interfaces:** +- Consumes `MockUSDC`, the V1 proxy, and a bounded set of four actor addresses. +- Produces independent ghost counters and handler accessors used by invariant assertions. +- Later Task 11 extends the same handler with V2 transfers. + +- [ ] Write `BankHandler` with four immutable actor addresses and only these V1 actions: + + - `deposit(uint256 actorSeed, uint256 amount)`: select an actor, bound amount to `[1, 10_000e6]`, mint to the actor, approve, deposit, and increment `ghostDeposited`. + - `withdraw(uint256 actorSeed, uint256 amount)`: select an actor, return early only when its internal balance is zero, bound to `[1, balance]`, withdraw, and increment `ghostWithdrawn`. + - `donate(uint256 actorSeed, uint256 amount)`: mint and directly transfer `[1, 1_000e6]` to the proxy, incrementing `ghostDonated` but not liabilities. + +Every action uses `vm.startPrank(actor)`/`vm.stopPrank()` in a balanced scope. The handler exposes the actor array so the invariant test, not the handler, sums on-chain balances. + +In invariant setup, transfer `MockUSDC` ownership to the handler so only the handler can mint bounded test liquidity. This avoids impersonating an owner inside actions and does not affect bank ownership. + +- [ ] Write failing invariant tests: + +```solidity +function invariant_liabilitiesEqualTrackedBalances() public view { + uint256 sum; + for (uint256 i; i < handler.actorCount(); ++i) { + sum += bank.balanceOf(handler.actorAt(i)); + } + assertEq(sum, bank.totalLiabilities()); +} + +function invariant_reservesCoverLiabilities() public view { + assertGe(token.balanceOf(address(bank)), bank.totalLiabilities()); +} + +function invariant_ghostAccountingMatchesChain() public view { + assertEq(handler.ghostDeposited() - handler.ghostWithdrawn(), bank.totalLiabilities()); + assertEq( + handler.ghostDeposited() + handler.ghostDonated() - handler.ghostWithdrawn(), + token.balanceOf(address(bank)) + ); +} +``` + +- [ ] Run red: + +```bash +npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force +``` + +Expected red: handler implementation is absent/incomplete. + +- [ ] Implement the minimum handler. Register it with `targetContract(address(handler))` and explicitly target only its three action selectors so inherited test/helper functions cannot enter the action space. + +- [ ] Run green and retain the call summary in output: + +```bash +npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force +``` + +Expected: all three invariants pass for `128` runs at depth `64`; the summary shows deposits, withdrawals, and donations were each exercised. + +- [ ] Commit: + +```bash +git add test/helpers/BankHandler.sol test/BankInvariant.t.sol +git commit -m "test: prove V1 accounting invariants" +``` + +--- + diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-5-report.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-5-report.md new file mode 100644 index 0000000..e861106 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-5-report.md @@ -0,0 +1,219 @@ +# Task 5 Report: V1 Stateful Accounting Invariants + +## Status + +Complete. Commit `6dcbb03` (`test: prove V1 accounting invariants`) adds only the two task-listed test files. Production contracts were not modified. + +## Handler design + +- `BankHandler` owns four fixed immutable actor addresses (`0x1001` through `0x1004`) and exposes the bounded actor set through `actorCount()` and `actorAt()`. +- `deposit` selects an actor from the seed, bounds the amount to `[1, 10_000e6]`, mints test liquidity, performs actor approval/deposit inside a balanced prank scope, and increments `ghostDeposited` only after success. +- `withdraw` selects an actor, returns early only for a zero bank balance, bounds the amount to `[1, balance]`, performs the withdrawal inside a balanced prank scope, and increments `ghostWithdrawn` only after success. +- `donate` selects an actor, bounds the amount to `[1, 1_000e6]`, mints test liquidity, directly transfers it to the proxy inside a balanced prank scope, and increments `ghostDonated` without touching liabilities. +- In setup, `MockUSDC` ownership transfers from the token owner to the handler. Bank ownership remains unchanged. +- The handler is registered with `targetContract(address(handler))`, and `targetSelector` restricts generated calls to exactly `deposit`, `withdraw`, and `donate`. + +## Invariant derivations + +1. Ledger conservation: the test independently enumerates the four actors and sums `bank.balanceOf(actor)`. That sum must equal `bank.totalLiabilities()`. +2. Solvency: the token's actual proxy balance must be greater than or equal to total liabilities. Direct donations create only surplus. +3. Independent flow accounting: + - `ghostDeposited - ghostWithdrawn == totalLiabilities` because only customer custody flows create or extinguish liabilities. + - `ghostDeposited + ghostDonated - ghostWithdrawn == proxy token reserves` because donations increase reserves without increasing liabilities. + +Ghost counters record successful handler inputs, while expected chain state is read from the real proxy and token. The handler does not mirror per-account bank balances, and the invariant test—not the handler—sums on-chain actor balances. + +## Changed files + +- `test/helpers/BankHandler.sol`: bounded V1 stateful action handler and independent ghost counters. +- `test/BankInvariant.t.sol`: handler setup, selector targeting, and three conservation/solvency invariants. + +## TDD evidence + +PATH was set in a separate persistent shell step: + +```text +export PATH=/tmp/codius-node-v24.18.0/bin:/home/golem/.foundry/bin:$PATH +``` + +### RED + +Exact command: + +```text +npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force +``` + +Output before either requested file existed: + +```text +[⠒] Compiling 28 files with Solc 0.8.35 +Compiler run successful! +No tests found in project! Forge looks for functions that start with `test` +``` + +This was the expected feature-absent failure: no invariant target or handler existed. + +### GREEN + +Exact command: + +```text +npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force +``` + +Key verbatim output: + +```text +Ran 3 tests for test/BankInvariant.t.sol:BankInvariantTest +[PASS] invariant_ghostAccountingMatchesChain() (runs: 128, calls: 8192, reverts: 0) +[PASS] invariant_liabilitiesEqualTrackedBalances() (runs: 128, calls: 8192, reverts: 0) +[PASS] invariant_reservesCoverLiabilities() (runs: 128, calls: 8192, reverts: 0) + +| Contract | Selector | Calls | Reverts | Discards | +| BankHandler | deposit | 2666 | 0 | 0 | +| BankHandler | donate | 2791 | 0 | 0 | +| BankHandler | withdraw | 2735 | 0 | 0 | + +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.43s (4.86s CPU time) +Ran 1 test suite in 3.43s (3.43s CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests) +``` + +The configured `128` runs and depth `64` produced `8,192` calls per invariant. The fixed seed yielded the same selector counts in the later full-suite run, proving all three actions were exercised. + +## Formatting and full-suite validation + +Formatting check after applying `forge fmt`: + +```text +forge fmt --check test/helpers/BankHandler.sol test/BankInvariant.t.sol +``` + +Output: empty, exit success. + +Full relevant suite command: + +```text +npm_config_offline=true forge test -vvv --force +``` + +Result: + +```text +Ran 4 test suites in 6.23s (12.11s CPU time): 43 tests passed, 0 failed, 0 skipped (43 total tests) +``` + +The invariant portion again passed all three properties for `128` runs and `8,192` calls each, with `2,666` deposits, `2,791` donations, `2,735` withdrawals, and zero reverts/discards. Compilation emitted only two pre-existing OpenZeppelin Foundry Upgrades mutability warnings in `StringFinder.sol` and `DefenderDeploy.sol`. + +## Mutation and self-review + +Mentally checked realistic mutations: + +- Omitting or mis-sizing a deposit/withdraw liability update breaks the ghost-liability equality. +- Updating aggregate liabilities without the selected account balance (or vice versa) breaks the summed-ledger equality. +- Failing to transfer reserves on deposit/withdraw breaks reserve ghost accounting and may break solvency. +- Treating donations as liabilities breaks both the ghost-liability equation and the intended surplus model. +- Draining or under-crediting proxy reserves breaks exact reserve ghost accounting and the solvency inequality. + +`git diff --check` and the final formatting check passed. Pre-commit status contained only the two task-listed files. The commit contains exactly those files and 122 inserted test lines. No production files changed. + +## Concerns + +None. The only warnings are existing third-party mutability warnings noted above. + +## Fix Round 1: Genuine handler-absent RED evidence + +The original RED evidence above is superseded by this fix round. A missing invariant test only established that Forge had no matching tests; it did not establish that the invariant specification failed when the handler feature was absent. + +### Covering test state + +An isolated temporary project was created at `/tmp/task-5-red.dLa7CJ`. It contained the committed `test/BankInvariant.t.sol` unchanged but deliberately excluded `test/helpers/BankHandler.sol`. The real worktree remained at commit `6dcbb03f3c79979416c2f0dd37556539276a4e29`, with its tracked files untouched. + +Exact isolation command: + +```text +rsync -a --exclude=.git --exclude=out --exclude=cache --exclude=test/helpers/BankHandler.sol ./ /tmp/task-5-red.dLa7CJ/ +``` + +Precondition checks confirmed: + +```text +isolated invariant test: present +isolated handler: absent +``` + +PATH was set separately in the isolated shell: + +```text +export PATH=/tmp/codius-node-v24.18.0/bin:/home/golem/.foundry/bin:$PATH +``` + +### Genuine RED + +The brief's command was then run literally in the isolated project: + +```text +npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force +``` + +Relevant verbatim output: + +```text +Compiler run failed: +Error (6275): Source "test/helpers/BankHandler.sol" not found: File not found. Searched the following locations: "/tmp/task-5-red.dLa7CJ". +ParserError: Source "test/helpers/BankHandler.sol" not found: File not found. Searched the following locations: "/tmp/task-5-red.dLa7CJ". + --> test/BankInvariant.t.sol:5:1: + | +5 | import {BankHandler} from "./helpers/BankHandler.sol"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Error: Compilation failed +``` + +Exit status was captured immediately afterward with `echo $?`: + +```text +1 +``` + +This is a genuine RED because the completed invariant test is present and compilation fails specifically at its dependency on the absent stateful handler. Adding `BankHandler.sol` is the production-of-the-test-suite change that makes this exact specification compile and run. + +### Fresh GREEN in the real worktree + +PATH was again set separately in a new shell rooted at the real worktree: + +```text +export PATH=/tmp/codius-node-v24.18.0/bin:/home/golem/.foundry/bin:$PATH +``` + +The brief's GREEN command was run literally: + +```text +npm_config_offline=true forge test --match-path test/BankInvariant.t.sol -vvv --force +``` + +Relevant verbatim output, repeated for each of the three invariants: + +```text +Ran 3 tests for test/BankInvariant.t.sol:BankInvariantTest +[PASS] invariant_ghostAccountingMatchesChain() (runs: 128, calls: 8192, reverts: 0) + +| Contract | Selector | Calls | Reverts | Discards | +| BankHandler | deposit | 2666 | 0 | 0 | +| BankHandler | donate | 2791 | 0 | 0 | +| BankHandler | withdraw | 2735 | 0 | 0 | + +[PASS] invariant_liabilitiesEqualTrackedBalances() (runs: 128, calls: 8192, reverts: 0) +[PASS] invariant_reservesCoverLiabilities() (runs: 128, calls: 8192, reverts: 0) + +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.53s (4.76s CPU time) +Ran 1 test suite in 3.53s (3.53s CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests) +``` + +Exit status captured immediately with `echo $?`: + +```text +0 +``` + +All three selectors were exercised over `128` runs at depth `64` (`8,192` calls per invariant), with no revert or discard. No tracked commit was needed for Fix Round 1 because the implementation was already correct; only the ignored evidence report changed. The deferred `actorAt` minor was not addressed. diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-6-brief.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-6-brief.md new file mode 100644 index 0000000..5e4ffb0 --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-6-brief.md @@ -0,0 +1,134 @@ +### Task 6: Deploy, seed, inspect, and export deterministic V1 state + +**Files:** +- Create: `script/lib/DemoScript.sol` +- Create: `script/DeployV1.s.sol` +- Create: `script/SeedV1Demo.s.sol` +- Create: `script/CheckState.s.sol` +- Create: `test/ScriptPreflight.t.sol` +- Create: `tools/finalize-manifest.mjs` +- Create: `tools/select-manifest.mjs` +- Create: `tools/test-finalize-manifest.mjs` +- Create: `deployments/.gitkeep` +- Modify: `Makefile` + +**Interfaces:** +- Consumes an RPC, chain ID, explicit broadcaster, pending manifest, and Foundry broadcast receipts. +- Produces an active schema-versioned public manifest only after receipt/code/postcondition checks, then the exact Act 1 state. +- Establishes local actor indexes: owner `0`, Alice `1`, Bob `2` from Anvil’s standard development mnemonic. + +- [ ] Write `ScriptPreflight.t.sol` around a small public harness for `DemoScript` and cover: + + - chain IDs `31337` and `84532` accepted; + - chain IDs `1`, `8453`, and an arbitrary value rejected before broadcast; + - missing/invalid manifest, wrong manifest chain, zero address, and address-without-code rejected; + - internal pending manifests may use deployment block `0`, while active manifests may not; + - local keys derivable only when chain ID is `31337`; + - serialized JSON contains public addresses but never the mnemonic, a private key, `PRIVATE_KEY`, or `MNEMONIC`. + +- [ ] Run red: + +```bash +forge test --match-path test/ScriptPreflight.t.sol -vvv --force +``` + +Expected red: `DemoScript` does not exist. + +- [ ] Implement `DemoScript` constants and guards: + +```solidity +uint256 internal constant ANVIL_CHAIN_ID = 31337; +uint256 internal constant BASE_SEPOLIA_CHAIN_ID = 84532; +string internal constant ANVIL_TEST_PHRASE = + "test test test test test test test test test test test junk"; +string internal constant PENDING_MANIFEST_PATH = "deployments/pending.json"; +string internal constant ACTIVE_MANIFEST_PATH = "deployments/active.json"; + +error UnsupportedChain(uint256 chainId); +error ManifestChainMismatch(uint256 expected, uint256 actual); +error MissingCode(string label, address target); +``` + +Provide narrow helpers for chain checks, `vm.deriveKey` on local only, manifest read/write through `vm.parseJson*`/`vm.serialize*`, code checks, and exact-state assertions. Resolve the input/output manifest from `DEPLOYMENT_MANIFEST_PATH` with a narrow default appropriate to each script. Never log or serialize a derived private key. + +- [ ] Write `DeployV1.s.sol` red tests/behavior first, then implement: + + 1. validate chain and resolve the public `SCRIPT_SENDER` value; + 2. start broadcast; + 3. deploy `MockUSDC(sender)`; + 4. call `Upgrades.deployUUPSProxy("BankV1.sol:BankV1", abi.encodeCall(...))`; + 5. stop broadcast; + 6. assert code, owner, asset, version `1`, and implementation identity; + 7. write `deployments/pending.json` with schema `1`, network, chain, deployment block `0`, display RPC/explorer metadata, token, proxy, implementation, owner, and local actor labels/addresses. + +On local, derive account `0`, require it equals `SCRIPT_SENDER`, and broadcast with that derived development key. On Base Sepolia, require `SCRIPT_SENDER` equals the expected owner and use the signer selected by Forge’s matching `--account`/`--sender` options; never read a raw signing secret from environment. + +- [ ] Test `finalize-manifest.mjs` and `select-manifest.mjs` with temporary pending/broadcast fixtures and an injected fake JSON-RPC function. Cover: a pre-broadcast deploy guard that rejects an existing target-chain canonical file, exact proxy transaction match, successful receipt, receipt-derived block, actual chain-ID match, code at token/proxy/implementation, implementation-slot match, failed/missing/ambiguous receipt, partial broadcast, wrong chain, missing code, an existing active manifest from either network remaining untouched until selection, secret-bearing content, atomic same-directory rename, and selection of only a valid confirmed chain manifest. Prove a failure never creates/changes a confirmed or active manifest, and selecting Base preserves Anvil byte-for-byte (and vice versa). + +- [ ] Implement the finalizer using Node standard modules and JSON-RPC `fetch`. Its network-free `preflight-deploy ` fails before Forge runs when the target canonical file exists and prints the exact safe recovery command (`make reset-local` or `make archive-base-manifest`). Deploy finalization reads `broadcast/DeployV1.s.sol//run-latest.json`, matches the transaction whose created address is the pending proxy, matches its transaction hash to a successful receipt, uses that receipt’s real block number, queries `eth_chainId`, `eth_getCode` for all three contracts, and queries the EIP-1967 implementation slot. Atomically write the chain’s canonical `anvil.json` or `base-sepolia.json` only after all checks pass. `select-manifest.mjs` validates a named canonical file and atomically copies it to `active.json`; it never deletes or overwrites the other chain. Never copy a credential-bearing terminal RPC into JSON. + +- [ ] Write and implement `SeedV1Demo.s.sol`, hard-guarded to chain `31337`. It must perform and assert this exact sequence: + +```text +owner mints Alice 2,000 mUSDC +owner mints Bob 1,000 mUSDC +Alice approves and deposits 1,000 mUSDC +Bob approves and deposits 500 mUSDC +Alice withdraws 100 mUSDC +``` + +Postconditions: Alice internal `900e6`, Bob internal `500e6`, liabilities `1_400e6`, reserves `1_400e6`, version `1`, no surplus. + +- [ ] Write and implement `CheckState.s.sol`. Always print network, block, token, proxy, implementation, owner, pause state, version, each configured actor balance, reserves, liabilities, and surplus. Always fail on `reserves < liabilities`, implementation/manifest mismatch, or manifest chain mismatch. `DEMO_EXPECTED_STAGE=deployed` asserts version `1` and empty accounting; `v1` asserts exact Act 1 values; `invariants` checks network-independent invariants only. Task 11 adds stage `v2`. + +- [ ] Add direct Make targets that do not start background processes yet: + +```make +RPC_LOCAL := http://127.0.0.1:8545 +ANVIL_OWNER := 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + +.PHONY: deploy-v1 seed-v1 check-state +deploy-v1: + @node tools/finalize-manifest.mjs preflight-deploy anvil + @SCRIPT_SENDER=$(ANVIL_OWNER) DEPLOYMENT_MANIFEST_PATH=deployments/pending.json npm_config_offline=true forge script script/DeployV1.s.sol:DeployV1 --rpc-url $(RPC_LOCAL) --sender $(ANVIL_OWNER) --broadcast --force + @DEPLOYMENT_MANIFEST_PATH=deployments/pending.json DEMO_EXPECTED_STAGE=deployed forge script script/CheckState.s.sol:CheckState --rpc-url $(RPC_LOCAL) --force + @node tools/finalize-manifest.mjs deploy --rpc-url $(RPC_LOCAL) + @node tools/select-manifest.mjs anvil +seed-v1: + @forge script script/SeedV1Demo.s.sol:SeedV1Demo --rpc-url $(RPC_LOCAL) --broadcast --force +check-state: + @DEMO_EXPECTED_STAGE=$${DEMO_EXPECTED_STAGE:-v1} forge script script/CheckState.s.sol:CheckState --rpc-url $(RPC_LOCAL) --force +``` + +- [ ] Run unit/preflight green checks: + +```bash +forge fmt +forge test --match-path test/ScriptPreflight.t.sol -vvv --force +``` + +- [ ] Run the first real local smoke test in two terminals. Terminal A: + +```bash +anvil --host 127.0.0.1 --port 8545 --chain-id 31337 +``` + +Terminal B: + +```bash +make deploy-v1 +make seed-v1 +DEMO_EXPECTED_STAGE=v1 make check-state +``` + +Expected: the exact Act 1 table prints and all commands exit `0`. Inspect identical `deployments/anvil.json` and selected `deployments/active.json`, verify their deployment block matches the confirmed proxy receipt, and verify they contain no secret material. Simulate a failed receipt fixture and confirm the finalizer changes neither confirmed nor active files. + +- [ ] Commit: + +```bash +git add script test/ScriptPreflight.t.sol tools/finalize-manifest.mjs tools/select-manifest.mjs tools/test-finalize-manifest.mjs deployments/.gitkeep Makefile +git commit -m "feat: script deterministic V1 demo state" +``` + +--- + diff --git a/.superpowers/sdd/2026-08-17-uups-bank-demo/task-6-report.md b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-6-report.md new file mode 100644 index 0000000..969356d --- /dev/null +++ b/.superpowers/sdd/2026-08-17-uups-bank-demo/task-6-report.md @@ -0,0 +1,34 @@ +# Task 6 report: deterministic V1 deployment state + +## Implemented + +- Preserved the pre-reboot Solidity work: `DemoScript`, `DeployV1`, `SeedV1Demo`, `CheckState`, and its 14-test preflight harness. +- Added the Node manifest control plane. `finalize-manifest.mjs` validates public manifests, blocks canonical overwrite before deploy, confirms the exact `CREATE` proxy transaction, receipt/block/chain/code/ERC-1967 slot, and atomically writes only the selected canonical manifest. `select-manifest.mjs` validates and atomically selects one canonical manifest without altering the other. +- Added fixture-driven Node tests, local Make targets, and `deployments/.gitkeep`. +- Completed a live Anvil deployment/seed/check smoke: confirmed and active manifests were byte-identical, their deployment block was receipt block `2`, and neither contained secret markers. Act 1 ended at Alice `900e6`, Bob `500e6`, liabilities/reserves `1_400e6`, surplus `0`. + +## Verification + +- `node tools/test-finalize-manifest.mjs` — 5/5 pass. +- `forge fmt --check` — pass. +- `forge test --match-path test/ScriptPreflight.t.sol -vvv --force` — 14/14 pass. +- `npm_config_offline=true forge test --force` — 57/57 pass (fresh final run). +- Live smoke (Anvil `31337`): `make deploy-v1`, `make seed-v1`, `DEMO_EXPECTED_STAGE=v1 make check-state` — all exit 0; receipt/manifest inspection passed. Anvil was stopped afterward. +- `git diff --check` — pass. + +## TDD evidence + +- Preserved pre-reboot Solidity RED/GREEN history honestly: it predates this resumed session; I only re-ran its green 14-test preflight suite. +- Node initial RED: `node --test tools/test-finalize-manifest.mjs` failed with `ERR_MODULE_NOT_FOUND` for `tools/finalize-manifest.mjs`, as expected before implementation. GREEN: the new test suite passed after implementing finalizer/selector. +- Atomic rename RED: the new atomic-writer test failed with `ENOENT` because the writer did not accept the injected I/O boundary; GREEN: it passed after staging a same-directory temporary path and renaming it. +- Proxy provenance RED: a `CALL` record bearing the proxy address was incorrectly accepted; GREEN: the finalizer now requires `transactionType === "CREATE"`, and all five Node tests pass. + +## Changed files + +`Makefile`; `deployments/.gitkeep`; `script/lib/DemoScript.sol`; `script/DeployV1.s.sol`; `script/SeedV1Demo.s.sol`; `script/CheckState.s.sol`; `test/ScriptPreflight.t.sol`; `tools/finalize-manifest.mjs`; `tools/select-manifest.mjs`; `tools/test-finalize-manifest.mjs`. + +## Self-review and concerns + +- Reviewed the scoped diff and real Foundry broadcast shape. The only finding was missing explicit `CREATE` filtering; fixed with a RED/GREEN regression test. +- Code-level Task 6 concerns: none. Base actor/RPC/keystore configuration was intentionally not added; it remains Task 12 per the stated ruling. +- Handoff blocker: staging/committing requires writing the Git worktree index outside the sandbox. The elevated `git add` request was rejected because the approval service reported a usage-limit failure. No files were staged or committed.