diff --git a/docs/SECURITY_AUDIT.md b/docs/SECURITY_AUDIT.md index 885bb00..e48097a 100644 --- a/docs/SECURITY_AUDIT.md +++ b/docs/SECURITY_AUDIT.md @@ -23,13 +23,13 @@ must be re-checked. CI enforces the baseline described here. | Severity | Before | After | | --------- | -----: | ----: | | Critical | 1 | 0 | -| High | 53 | 1 | +| High | 53 | 0 | | Moderate | 86 | 8 | | Low | 7 | 2 | -| **Total** | 147 | 11 | +| **Total** | 147 | 10 | -The single remaining high finding (`braces`) has no patched release upstream and -is documented below. The critical (`shell-quote`) was removed by an override. +All high findings have been remediated. The critical (`shell-quote`) was removed +by an override. ## How to run the audit @@ -92,11 +92,10 @@ different consumer: `react-devtools-core`, `@walletconnect/jsonrpc-ws-connection`) resolve to the patched `7.5.13` from their own range. Only the exact/tilde-pinned `ws` 8 consumers (`engine.io-client`, `ethers`, `viem`) are bumped, within major 8. -- **`image-size` and `toml` have no patched release inside the major the - consumer declares** (`image-size` 1.x, `toml` 3.x), so those two overrides do - cross a major. They are scoped to `metro` and `@stellar/stellar-sdk` - respectively, and `scripts/dependency-compatibility.test.mjs` exercises each - affected path so the bump cannot regress silently. +- **`image-size` has no patched release inside the major the consumer + declares** (`image-size` 1.x), so that override does cross a major. It is + scoped to `metro`, and `scripts/dependency-compatibility.test.mjs` exercises + the affected path so the bump cannot regress silently. The `toml` override is safe because `@stellar/stellar-sdk` only uses `toml` inside `StellarToml.Resolver`, which the demo never calls (`@stellar/stellar-sdk` @@ -114,25 +113,17 @@ consumer declares, and would require a breaking major bump of a package we do not control. All are accepted with an owner and an expiry; re-check on or before the expiry. -| Package | Severity | Advisory | Dependency path | Owner | Expiry | -| ---------------------- | -------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------ | ---------- | -| `braces` | high | GHSA-vfj7-8cjw-p6xm | `@solana/wallet-adapter-react` → `@solana-mobile/wallet-adapter-mobile` → `react-native` → `metro` → `micromatch` | `@wraith-protocol/maintainers` | 2027-01-15 | -| `uuid` | moderate | GHSA-w5hq-g745-h8pq | `@solana/web3.js` → `jayson`/`rpc-websockets`; `wagmi` → `@metamask/utils` | `@wraith-protocol/maintainers` | 2027-01-15 | -| `valibot` | moderate | GHSA-5qjj-4xww-7phc | `@ckb-ccc/connector-react` → `@ckb-ccc/connector` → `@ckb-ccc/ccc` → `@ckb-ccc/xverse` | `@wraith-protocol/maintainers` | 2027-01-15 | -| `decode-uri-component` | moderate | GHSA-vcc3-ghjq-m6fr | `wagmi` → `@walletconnect/ethereum-provider` → `@reown/appkit` → `query-string` | `@wraith-protocol/maintainers` | 2027-01-15 | -| `stream-json` | moderate | GHSA-528h-pc64-c93x, GHSA-hqr4-qq8f-hg3x, GHSA-mjw6-4jj6-33hc | `@solana/web3.js` → `jayson` → `stream-json` | `@wraith-protocol/maintainers` | 2027-01-15 | -| `elliptic` | low | GHSA-848j-6mx2-7j84 | `@ckb-ccc/ccc` → `@ckb-ccc/eip6963` → `@joyid/ckb` → `@nervosnetwork/ckb-sdk-utils` | `@wraith-protocol/maintainers` | 2027-01-15 | -| `@babel/core` | low | GHSA-4x5r-pxfx-6jf8 | `@solana/wallet-adapter-react` → `react-native` → `@react-native/codegen` | `@wraith-protocol/maintainers` | 2027-01-15 | +| Package | Severity | Advisory | Dependency path | Owner | Expiry | +| ---------------------- | -------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------ | ---------- | +| `uuid` | moderate | GHSA-w5hq-g745-h8pq | `@solana/web3.js` → `jayson`/`rpc-websockets`; `wagmi` → `@metamask/utils` | `@wraith-protocol/maintainers` | 2027-01-15 | +| `valibot` | moderate | GHSA-5qjj-4xww-7phc | `@ckb-ccc/connector-react` → `@ckb-ccc/connector` → `@ckb-ccc/ccc` → `@ckb-ccc/xverse` | `@wraith-protocol/maintainers` | 2027-01-15 | +| `decode-uri-component` | moderate | GHSA-vcc3-ghjq-m6fr | `wagmi` → `@walletconnect/ethereum-provider` → `@reown/appkit` → `query-string` | `@wraith-protocol/maintainers` | 2027-01-15 | +| `stream-json` | moderate | GHSA-528h-pc64-c93x, GHSA-hqr4-qq8f-hg3x, GHSA-mjw6-4jj6-33hc | `@solana/web3.js` → `jayson` → `stream-json` | `@wraith-protocol/maintainers` | 2027-01-15 | +| `elliptic` | low | GHSA-848j-6mx2-7j84 | `@ckb-ccc/ccc` → `@ckb-ccc/eip6963` → `@joyid/ckb` → `@nervosnetwork/ckb-sdk-utils` | `@wraith-protocol/maintainers` | 2027-01-15 | +| `@babel/core` | low | GHSA-4x5r-pxfx-6jf8 | `@solana/wallet-adapter-react` → `react-native` → `@react-native/codegen` | `@wraith-protocol/maintainers` | 2027-01-15 | Notes: -- `braces` is reached only through the React Native/Metro tooling pulled in by - the Solana Mobile wallet adapter; it is build-time code and is not part of the - browser bundle shipped by `vite build`. -- `braces` is currently unfixable: the latest published release (`3.0.3`) is the - affected version. This entry should be removed as soon as a patched release is - published. - ## CI gate `.github/workflows/ci.yml` runs `pnpm audit:gate` on every push and pull request @@ -140,7 +131,8 @@ to `main`/`develop`. The gate lives in `scripts/audit-gate.mjs` and enforces the baseline: - **critical ≤ 0** — a new critical finding always fails CI. -- **high ≤ 1** — the single accepted `braces` finding above. +- **high ≤ 0** — all high findings have been remediated; any new high finding + fails CI. Raising either baseline requires a matching entry (owner + expiry) in the table above. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8291c8b..d68b91e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,10 +27,10 @@ importers: version: 0.13.0 '@ckb-ccc/ccc': specifier: ^1.3.6 - version: 1.3.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + version: 1.3.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@ckb-ccc/connector-react': specifier: ^1.0.34 - version: 1.0.34(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + version: 1.0.34(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@noble/curves': specifier: ^1.9.7 version: 1.9.7 @@ -39,7 +39,7 @@ importers: version: 1.8.0 '@rainbow-me/rainbowkit': specifier: ^2.2.11 - version: 2.2.11(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(wagmi@2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6)) + version: 2.2.11(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(wagmi@2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(zod@4.6.6)) '@solana/wallet-adapter-base': specifier: ^0.9.28 version: 0.9.28(@solana/web3.js@1.99.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)) @@ -69,13 +69,13 @@ importers: version: 3.14.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@walletconnect/core': specifier: ^2.26.0 - version: 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + version: 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/sign-client': specifier: ^2.26.0 - version: 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + version: 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@wraith-protocol/sdk': specifier: ^1.4.5 - version: 1.4.5(@solana/web3.js@1.99.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6))(@stellar/stellar-sdk@13.3.0)(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + version: 1.4.5(@solana/web3.js@1.99.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6))(@stellar/stellar-sdk@13.3.0)(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@zxing/browser': specifier: 0.0.7 version: 0.0.7(@zxing/library@0.18.6) @@ -114,10 +114,10 @@ importers: version: 7.18.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) viem: specifier: ^2.57.7 - version: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + version: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) wagmi: specifier: ^2.19.5 - version: 2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6) + version: 2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(zod@4.6.6) workbox-cacheable-response: specifier: ^7.4.1 version: 7.4.1 @@ -3451,7 +3451,7 @@ packages: resolution: {integrity: sha512-wQ7hV8Yg/yKmGyFpqrNZufCxbszDe5es4AZGYPBitocfSqXtjrTG9JMWFcc4N30ukl2ve48aBTwt7NJxVQdU3w==} peerDependencies: typescript: '>=5.0.4' - zod: ^3 >=3.19.1 + zod: 4.6.6 peerDependenciesMeta: zod: optional: true @@ -3460,7 +3460,7 @@ packages: resolution: {integrity: sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==} peerDependencies: typescript: '>=5.0.4' - zod: ^3 >=3.22.0 + zod: 4.6.6 peerDependenciesMeta: typescript: optional: true @@ -3471,7 +3471,7 @@ packages: resolution: {integrity: sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==} peerDependencies: typescript: '>=5.0.4' - zod: ^3 >=3.22.0 + zod: 4.6.6 peerDependenciesMeta: typescript: optional: true @@ -3482,7 +3482,7 @@ packages: resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} peerDependencies: typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 + zod: 4.6.6 peerDependenciesMeta: typescript: optional: true @@ -8033,14 +8033,8 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} - zod@3.22.4: - resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.6.6: + resolution: {integrity: sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==} zustand@5.0.0: resolution: {integrity: sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==} @@ -8966,16 +8960,16 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@base-org/account@2.4.0(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.3.6)': + '@base-org/account@2.4.0(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@coinbase/cdp-sdk': 1.47.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) '@noble/hashes': 1.4.0 clsx: 1.2.1 eventemitter3: 5.0.1 idb-keyval: 6.2.1 - ox: 0.6.9(typescript@5.9.3)(zod@4.3.6) + ox: 0.6.9(typescript@5.9.3)(zod@4.6.6) preact: 10.24.2 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) zustand: 5.0.3(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.4.0(react@19.2.5)) transitivePeerDependencies: - '@types/react' @@ -8998,17 +8992,17 @@ snapshots: dependencies: css-tree: 3.2.1 - '@ckb-ccc/ccc@1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/ccc@1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/eip6963': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/joy-id': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/nip07': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/okx': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/rei': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/shell': 1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/uni-sat': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/utxo-global': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/xverse': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/eip6963': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/joy-id': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/nip07': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/okx': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/rei': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/shell': 1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/uni-sat': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/utxo-global': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/xverse': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - debug @@ -9018,17 +9012,17 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/ccc@1.3.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/ccc@1.3.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/eip6963': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/joy-id': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/nip07': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/okx': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/rei': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/shell': 1.3.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/uni-sat': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/utxo-global': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/xverse': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/eip6963': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/joy-id': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/nip07': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/okx': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/rei': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/shell': 1.3.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/uni-sat': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/utxo-global': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/xverse': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - debug @@ -9038,9 +9032,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/connector-react@1.0.34(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/connector-react@1.0.34(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/connector': 1.0.33(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/connector': 1.0.33(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@lit/react': 1.0.8(@types/react@19.2.14) react: 19.2.5 transitivePeerDependencies: @@ -9053,9 +9047,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/connector@1.0.33(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/connector@1.0.33(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/ccc': 1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/ccc': 1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) lit: 3.3.2 transitivePeerDependencies: - bufferutil @@ -9066,9 +9060,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/core@1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/core@1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.3.6) + '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.6.6) '@noble/ciphers': 0.5.3 '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 @@ -9085,9 +9079,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/core@1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/core@1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.3.6) + '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.6.6) '@noble/ciphers': 2.4.0 '@noble/curves': 2.4.0 '@noble/hashes': 2.4.0 @@ -9104,10 +9098,10 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/did-ckb@0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/did-ckb@0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/type-id': 0.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/type-id': 0.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@ipld/dag-cbor': 10.0.2 '@noble/curves': 2.4.0 transitivePeerDependencies: @@ -9117,9 +9111,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/eip6963@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/eip6963@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9127,9 +9121,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/eip6963@1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/eip6963@1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9137,11 +9131,11 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/joy-id@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/joy-id@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.3.6) - '@joyid/common': 0.2.2(typescript@5.9.3)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.6.6) + '@joyid/common': 0.2.2(typescript@5.9.3)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9149,11 +9143,11 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/joy-id@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/joy-id@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.3.6) - '@joyid/common': 0.2.2(typescript@5.9.3)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@joyid/ckb': 1.1.4(typescript@5.9.3)(zod@4.6.6) + '@joyid/common': 0.2.2(typescript@5.9.3)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9161,9 +9155,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/nip07@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/nip07@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9171,9 +9165,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/nip07@1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/nip07@1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9181,11 +9175,11 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/okx@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/okx@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/nip07': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/uni-sat': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/nip07': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/uni-sat': 1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9193,11 +9187,11 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/okx@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/okx@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/nip07': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/uni-sat': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/nip07': 1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/uni-sat': 1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9205,9 +9199,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/rei@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/rei@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9215,9 +9209,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/rei@1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/rei@1.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9225,12 +9219,12 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/shell@1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/shell@1.1.25(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/spore': 1.5.17(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/ssri': 0.2.22(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/udt': 0.1.24(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/spore': 1.5.17(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/ssri': 0.2.22(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/udt': 0.1.24(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - debug @@ -9240,14 +9234,14 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/shell@1.3.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/shell@1.3.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/did-ckb': 0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/spore': 1.6.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/ssri': 0.4.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/type-id': 0.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/udt': 0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/did-ckb': 0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/spore': 1.6.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/ssri': 0.4.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/type-id': 0.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/udt': 0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - debug @@ -9257,9 +9251,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/spore@1.5.17(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/spore@1.5.17(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) axios: 1.20.0 transitivePeerDependencies: - bufferutil @@ -9270,9 +9264,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/spore@1.6.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/spore@1.6.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) axios: 1.20.0 transitivePeerDependencies: - bufferutil @@ -9283,9 +9277,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/ssri@0.2.22(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/ssri@0.2.22(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9293,9 +9287,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/ssri@0.4.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/ssri@0.4.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9303,9 +9297,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/type-id@0.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/type-id@0.1.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9313,10 +9307,10 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/udt@0.1.24(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/udt@0.1.24(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/ssri': 0.2.22(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/ssri': 0.2.22(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9324,10 +9318,10 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/udt@0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/udt@0.2.15(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@ckb-ccc/ssri': 0.4.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@ckb-ccc/ssri': 0.4.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9335,9 +9329,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/uni-sat@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/uni-sat@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9345,9 +9339,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/uni-sat@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/uni-sat@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9355,9 +9349,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/utxo-global@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/utxo-global@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9365,9 +9359,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/utxo-global@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/utxo-global@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - encoding @@ -9375,9 +9369,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/xverse@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/xverse@1.0.32(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.12.5(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) valibot: 1.3.1(typescript@5.9.3) transitivePeerDependencies: - bufferutil @@ -9386,9 +9380,9 @@ snapshots: - utf-8-validate - zod - '@ckb-ccc/xverse@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@ckb-ccc/xverse@1.2.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@ckb-ccc/core': 1.23.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) bitcoinjs-lib: 7.0.4(typescript@5.9.3) valibot: 1.5.0(typescript@5.9.3) transitivePeerDependencies: @@ -9403,14 +9397,14 @@ snapshots: '@solana-program/system': 0.10.0(@solana/kit@5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)) '@solana-program/token': 0.9.0(@solana/kit@5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)) '@solana/kit': 5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) - abitype: 1.0.6(typescript@5.9.3)(zod@3.25.76) + abitype: 1.0.6(typescript@5.9.3)(zod@4.6.6) axios: 1.20.0 axios-retry: 4.5.0(axios@1.20.0) jose: 6.2.2 md5: 2.3.0 uncrypto: 0.1.3 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76) - zod: 3.25.76 + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + zod: 4.6.6 transitivePeerDependencies: - bufferutil - debug @@ -9433,15 +9427,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@coinbase/wallet-sdk@4.3.6(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.3.6)': + '@coinbase/wallet-sdk@4.3.6(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@noble/hashes': 1.4.0 clsx: 1.2.1 eventemitter3: 5.0.1 idb-keyval: 6.2.1 - ox: 0.6.9(typescript@5.9.3)(zod@4.3.6) + ox: 0.6.9(typescript@5.9.3)(zod@4.6.6) preact: 10.24.2 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) zustand: 5.0.3(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.4.0(react@19.2.5)) transitivePeerDependencies: - '@types/react' @@ -9695,11 +9689,11 @@ snapshots: optionalDependencies: '@noble/hashes': 1.8.0 - '@gemini-wallet/core@0.3.2(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))': + '@gemini-wallet/core@0.3.2(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))': dependencies: '@metamask/rpc-errors': 7.0.2 eventemitter3: 5.0.1 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - supports-color @@ -9982,9 +9976,9 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@joyid/ckb@1.1.4(typescript@5.9.3)(zod@4.3.6)': + '@joyid/ckb@1.1.4(typescript@5.9.3)(zod@4.6.6)': dependencies: - '@joyid/common': 0.2.2(typescript@5.9.3)(zod@4.3.6) + '@joyid/common': 0.2.2(typescript@5.9.3)(zod@4.6.6) '@nervosnetwork/ckb-sdk-utils': 0.109.5 cross-fetch: 4.0.0 uncrypto: 0.1.3 @@ -9993,9 +9987,9 @@ snapshots: - typescript - zod - '@joyid/common@0.2.2(typescript@5.9.3)(zod@4.3.6)': + '@joyid/common@0.2.2(typescript@5.9.3)(zod@4.6.6)': dependencies: - abitype: 0.8.7(typescript@5.9.3)(zod@4.3.6) + abitype: 0.8.7(typescript@5.9.3)(zod@4.6.6) type-fest: 4.6.0 transitivePeerDependencies: - typescript @@ -10327,7 +10321,7 @@ snapshots: dependencies: playwright: 1.61.1 - '@rainbow-me/rainbowkit@2.2.11(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(wagmi@2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6))': + '@rainbow-me/rainbowkit@2.2.11(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@5.9.3)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(wagmi@2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(zod@4.6.6))': dependencies: '@tanstack/react-query': 5.104.3(react@19.2.5) '@vanilla-extract/css': 1.20.1 @@ -10339,8 +10333,8 @@ snapshots: react-dom: 19.2.5(react@19.2.5) react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) ua-parser-js: 2.0.10 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - wagmi: 2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + wagmi: 2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(zod@4.6.6) transitivePeerDependencies: - '@types/react' - babel-plugin-macros @@ -10422,35 +10416,24 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@reown/appkit-common@1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.22.4)': + '@reown/appkit-common@1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: big.js: 6.2.2 dayjs: 1.11.13 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.22.4) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - zod - '@reown/appkit-common@1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@reown/appkit-controllers@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - big.js: 6.2.2 - dayjs: 1.11.13 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate - - zod - - '@reown/appkit-controllers@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': - dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@reown/appkit-wallet': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) - '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) valtio: 1.13.2(@types/react@19.2.14)(react@19.2.5) - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -10479,12 +10462,12 @@ snapshots: - utf-8-validate - zod - '@reown/appkit-pay@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@reown/appkit-pay@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.3.6) + '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.6.6) lit: 3.3.0 valtio: 1.13.2(@types/react@19.2.14)(react@19.2.5) transitivePeerDependencies: @@ -10519,12 +10502,12 @@ snapshots: dependencies: buffer: 6.0.3 - '@reown/appkit-scaffold-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.3.6)': + '@reown/appkit-scaffold-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.6.6)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.3.6) + '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.6.6) '@reown/appkit-wallet': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) lit: 3.3.0 transitivePeerDependencies: @@ -10556,10 +10539,10 @@ snapshots: - valtio - zod - '@reown/appkit-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@reown/appkit-ui@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@reown/appkit-wallet': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) lit: 3.3.0 qrcode: 1.5.3 @@ -10591,16 +10574,16 @@ snapshots: - utf-8-validate - zod - '@reown/appkit-utils@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.3.6)': + '@reown/appkit-utils@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.6.6)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@reown/appkit-polyfills': 1.7.8 '@reown/appkit-wallet': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) '@walletconnect/logger': 2.1.2 - '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) valtio: 1.13.2(@types/react@19.2.14)(react@19.2.5) - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -10631,30 +10614,30 @@ snapshots: '@reown/appkit-wallet@1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.22.4) + '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@reown/appkit-polyfills': 1.7.8 '@walletconnect/logger': 2.1.2 - zod: 3.22.4 + zod: 4.6.6 transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - '@reown/appkit@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@reown/appkit@1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-pay': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@reown/appkit-common': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-controllers': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-pay': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@reown/appkit-polyfills': 1.7.8 - '@reown/appkit-scaffold-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.3.6) - '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.3.6) + '@reown/appkit-scaffold-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.6.6) + '@reown/appkit-ui': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@reown/appkit-utils': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(valtio@1.13.2(@types/react@19.2.14)(react@19.2.5))(zod@4.6.6) '@reown/appkit-wallet': 1.7.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/universal-provider': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) bs58: 6.0.0 valtio: 1.13.2(@types/react@19.2.14)(react@19.2.5) - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -10804,9 +10787,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.1': optional: true - '@safe-global/safe-apps-provider@0.18.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@safe-global/safe-apps-provider@0.18.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) events: 3.3.0 transitivePeerDependencies: - bufferutil @@ -10814,10 +10797,10 @@ snapshots: - utf-8-validate - zod - '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.23.1 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - bufferutil - typescript @@ -12256,19 +12239,19 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@wagmi/connectors@6.2.0(f3d86e2a5fe5f0e9cae58dd01a32a247)': + '@wagmi/connectors@6.2.0(91b15c8e527d81870b15108fe114c24a)': dependencies: - '@base-org/account': 2.4.0(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.3.6) - '@coinbase/wallet-sdk': 4.3.6(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.3.6) - '@gemini-wallet/core': 0.3.2(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)) + '@base-org/account': 2.4.0(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.6.6) + '@coinbase/wallet-sdk': 4.3.6(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(utf-8-validate@6.0.6)(zod@4.6.6) + '@gemini-wallet/core': 0.3.2(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)) '@metamask/sdk': 0.33.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) - '@safe-global/safe-apps-provider': 0.18.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@wagmi/core': 2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)) - '@walletconnect/ethereum-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@safe-global/safe-apps-provider': 0.18.6(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@wagmi/core': 2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)) + '@walletconnect/ethereum-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) cbw-sdk: '@coinbase/wallet-sdk@3.9.3' - porto: 0.2.35(20925074f885804a1a85057362670144) - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + porto: 0.2.35(8d6b61dcfd8f1a51dd1bab10f6226214) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -12309,11 +12292,11 @@ snapshots: - wagmi - zod - '@wagmi/core@2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))': + '@wagmi/core@2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))': dependencies: eventemitter3: 5.0.1 mipd: 0.0.7(typescript@5.9.3) - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) zustand: 5.0.0(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.4.0(react@19.2.5)) optionalDependencies: '@tanstack/query-core': 5.104.3 @@ -12351,7 +12334,7 @@ snapshots: dependencies: '@wallet-standard/base': 1.1.0 - '@walletconnect/core@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/core@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 @@ -12365,7 +12348,7 @@ snapshots: '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.33.0 events: 3.3.0 @@ -12395,7 +12378,7 @@ snapshots: - utf-8-validate - zod - '@walletconnect/core@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/core@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 @@ -12409,7 +12392,7 @@ snapshots: '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.33.0 events: 3.3.0 @@ -12439,7 +12422,7 @@ snapshots: - utf-8-validate - zod - '@walletconnect/core@2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/core@2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-provider': 1.0.14 @@ -12453,7 +12436,7 @@ snapshots: '@walletconnect/safe-json': 1.0.2 '@walletconnect/time': 1.0.2 '@walletconnect/types': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(typescript@5.9.3)(zod@4.3.6) + '@walletconnect/utils': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(typescript@5.9.3)(zod@4.6.6) '@walletconnect/window-getters': 1.0.1 es-toolkit: 1.45.1 events: 3.3.0 @@ -12487,18 +12470,18 @@ snapshots: dependencies: tslib: 1.14.1 - '@walletconnect/ethereum-provider@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/ethereum-provider@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@reown/appkit': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@reown/appkit': 1.7.8(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/jsonrpc-http-connection': 1.0.8 '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/universal-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/universal-provider': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -12628,16 +12611,16 @@ snapshots: dependencies: tslib: 1.14.1 - '@walletconnect/sign-client@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/sign-client@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@walletconnect/core': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/core': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/logger': 2.1.2 '@walletconnect/time': 1.0.2 '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -12664,16 +12647,16 @@ snapshots: - utf-8-validate - zod - '@walletconnect/sign-client@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/sign-client@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@walletconnect/core': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/core': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/events': 1.0.1 '@walletconnect/heartbeat': 1.2.2 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/logger': 2.1.2 '@walletconnect/time': 1.0.2 '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -12700,14 +12683,14 @@ snapshots: - utf-8-validate - zod - '@walletconnect/sign-client@2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/sign-client@2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: - '@walletconnect/core': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/core': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/logger': 3.0.2 '@walletconnect/time': 1.0.2 '@walletconnect/types': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(typescript@5.9.3)(zod@4.3.6) + '@walletconnect/utils': 2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(typescript@5.9.3)(zod@4.6.6) events: 3.3.0 transitivePeerDependencies: - '@azure/app-configuration' @@ -12825,7 +12808,7 @@ snapshots: - ioredis - uploadthing - '@walletconnect/universal-provider@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/universal-provider@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8 @@ -12834,9 +12817,9 @@ snapshots: '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/sign-client': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/types': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/utils': 2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) es-toolkit: 1.33.0 events: 3.3.0 transitivePeerDependencies: @@ -12865,7 +12848,7 @@ snapshots: - utf-8-validate - zod - '@walletconnect/universal-provider@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/universal-provider@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@walletconnect/events': 1.0.1 '@walletconnect/jsonrpc-http-connection': 1.0.8 @@ -12874,9 +12857,9 @@ snapshots: '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/keyvaluestorage': 1.1.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) '@walletconnect/logger': 2.1.2 - '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/sign-client': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) '@walletconnect/types': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))) - '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + '@walletconnect/utils': 2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) es-toolkit: 1.33.0 events: 3.3.0 transitivePeerDependencies: @@ -12905,7 +12888,7 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/utils@2.21.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@noble/ciphers': 1.2.1 '@noble/curves': 1.8.1 @@ -12923,7 +12906,7 @@ snapshots: detect-browser: 5.3.0 query-string: 7.1.3 uint8arrays: 3.1.0 - viem: 2.23.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.23.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -12949,7 +12932,7 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@walletconnect/utils@2.21.1(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@noble/ciphers': 1.2.1 '@noble/curves': 1.8.1 @@ -12967,7 +12950,7 @@ snapshots: detect-browser: 5.3.0 query-string: 7.1.3 uint8arrays: 3.1.0 - viem: 2.23.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.23.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -12993,7 +12976,7 @@ snapshots: - utf-8-validate - zod - '@walletconnect/utils@2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(typescript@5.9.3)(zod@4.3.6)': + '@walletconnect/utils@2.26.0(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(typescript@5.9.3)(zod@4.6.6)': dependencies: '@msgpack/msgpack': 3.1.3 '@noble/ciphers': 1.3.0 @@ -13011,7 +12994,7 @@ snapshots: '@walletconnect/window-metadata': 1.0.1 blakejs: 1.2.1 detect-browser: 5.3.0 - ox: 0.9.3(typescript@5.9.3)(zod@4.3.6) + ox: 0.9.3(typescript@5.9.3)(zod@4.6.6) uint8arrays: 3.1.1 transitivePeerDependencies: - '@azure/app-configuration' @@ -13045,11 +13028,11 @@ snapshots: '@walletconnect/window-getters': 1.0.1 tslib: 1.14.1 - '@wraith-protocol/sdk@1.4.5(@solana/web3.js@1.99.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6))(@stellar/stellar-sdk@13.3.0)(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)': + '@wraith-protocol/sdk@1.4.5(@solana/web3.js@1.99.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6))(@stellar/stellar-sdk@13.3.0)(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)': dependencies: '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) optionalDependencies: '@solana/web3.js': 1.99.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) '@stellar/stellar-sdk': 13.3.0 @@ -13079,36 +13062,26 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 - abitype@0.8.7(typescript@5.9.3)(zod@4.3.6): + abitype@0.8.7(typescript@5.9.3)(zod@4.6.6): dependencies: typescript: 5.9.3 optionalDependencies: - zod: 4.3.6 + zod: 4.6.6 - abitype@1.0.6(typescript@5.9.3)(zod@3.25.76): + abitype@1.0.6(typescript@5.9.3)(zod@4.6.6): optionalDependencies: typescript: 5.9.3 - zod: 3.25.76 + zod: 4.6.6 - abitype@1.0.8(typescript@5.9.3)(zod@4.3.6): + abitype@1.0.8(typescript@5.9.3)(zod@4.6.6): optionalDependencies: typescript: 5.9.3 - zod: 4.3.6 + zod: 4.6.6 - abitype@1.2.3(typescript@5.9.3)(zod@3.22.4): + abitype@1.2.3(typescript@5.9.3)(zod@4.6.6): optionalDependencies: typescript: 5.9.3 - zod: 3.22.4 - - abitype@1.2.3(typescript@5.9.3)(zod@3.25.76): - optionalDependencies: - typescript: 5.9.3 - zod: 3.25.76 - - abitype@1.2.3(typescript@5.9.3)(zod@4.3.6): - optionalDependencies: - typescript: 5.9.3 - zod: 4.3.6 + zod: 4.6.6 abort-controller@3.0.0: dependencies: @@ -16244,7 +16217,7 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - ox@0.14.56(typescript@5.9.3)(zod@3.22.4): + ox@0.14.56(typescript@5.9.3)(zod@4.6.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -16252,72 +16225,42 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@3.22.4) + abitype: 1.2.3(typescript@5.9.3)(zod@4.6.6) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - zod - ox@0.14.56(typescript@5.9.3)(zod@3.25.76): - dependencies: - '@adraffy/ens-normalize': 1.11.1 - '@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(typescript@5.9.3)(zod@3.25.76) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - zod - - ox@0.14.56(typescript@5.9.3)(zod@4.3.6): - dependencies: - '@adraffy/ens-normalize': 1.11.1 - '@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(typescript@5.9.3)(zod@4.3.6) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - zod - - ox@0.6.7(typescript@5.9.3)(zod@4.3.6): + ox@0.6.7(typescript@5.9.3)(zod@4.6.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.6.6) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - zod - ox@0.6.9(typescript@5.9.3)(zod@4.3.6): + ox@0.6.9(typescript@5.9.3)(zod@4.6.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.6.6) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - zod - ox@0.9.17(typescript@5.9.3)(zod@4.3.6): + ox@0.9.17(typescript@5.9.3)(zod@4.6.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -16325,14 +16268,14 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.6.6) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - zod - ox@0.9.3(typescript@5.9.3)(zod@4.3.6): + ox@0.9.3(typescript@5.9.3)(zod@4.6.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -16340,7 +16283,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.6.6) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 @@ -16521,22 +16464,22 @@ snapshots: transitivePeerDependencies: - supports-color - porto@0.2.35(20925074f885804a1a85057362670144): + porto@0.2.35(8d6b61dcfd8f1a51dd1bab10f6226214): dependencies: - '@wagmi/core': 2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)) + '@wagmi/core': 2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)) hono: 4.13.13 idb-keyval: 6.2.2 mipd: 0.0.7(typescript@5.9.3) - ox: 0.9.17(typescript@5.9.3)(zod@4.3.6) - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) - zod: 4.3.6 + ox: 0.9.17(typescript@5.9.3)(zod@4.6.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) + zod: 4.6.6 zustand: 5.0.14(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.4.0(react@19.2.5)) optionalDependencies: '@tanstack/react-query': 5.104.3(react@19.2.5) react: 19.2.5 react-native: 0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6) typescript: 5.9.3 - wagmi: 2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6) + wagmi: 2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(zod@4.6.6) transitivePeerDependencies: - '@types/react' - immer @@ -17815,49 +17758,15 @@ snapshots: dependencies: uint8array-tools: 0.0.10 - viem@2.23.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6): + viem@2.23.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6): dependencies: '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 '@scure/bip32': 1.6.2 '@scure/bip39': 1.5.4 - abitype: 1.0.8(typescript@5.9.3)(zod@4.3.6) + abitype: 1.0.8(typescript@5.9.3)(zod@4.6.6) isows: 1.0.6(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - ox: 0.6.7(typescript@5.9.3)(zod@4.3.6) - ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - - viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.22.4): - 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(typescript@5.9.3)(zod@3.22.4) - isows: 1.0.7(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - ox: 0.14.56(typescript@5.9.3)(zod@3.22.4) - ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - - viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@3.25.76): - 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(typescript@5.9.3)(zod@3.25.76) - isows: 1.0.7(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - ox: 0.14.56(typescript@5.9.3)(zod@3.25.76) + ox: 0.6.7(typescript@5.9.3)(zod@4.6.6) ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: typescript: 5.9.3 @@ -17866,15 +17775,15 @@ snapshots: - utf-8-validate - zod - viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6): + viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6): 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(typescript@5.9.3)(zod@4.3.6) + abitype: 1.2.3(typescript@5.9.3)(zod@4.6.6) isows: 1.0.7(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - ox: 0.14.56(typescript@5.9.3)(zod@4.3.6) + ox: 0.14.56(typescript@5.9.3)(zod@4.6.6) ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: typescript: 5.9.3 @@ -17946,14 +17855,14 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - wagmi@2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6))(zod@4.3.6): + wagmi@2.19.5(@react-native-async-storage/async-storage@1.24.0(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6)))(@tanstack/query-core@5.104.3)(@tanstack/react-query@5.104.3(react@19.2.5))(@types/react@19.2.14)(bufferutil@4.1.0)(react-native@0.85.1(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.5)(utf-8-validate@6.0.6))(react@19.2.5)(typescript@5.9.3)(utf-8-validate@6.0.6)(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6))(zod@4.6.6): dependencies: '@tanstack/react-query': 5.104.3(react@19.2.5) - '@wagmi/connectors': 6.2.0(f3d86e2a5fe5f0e9cae58dd01a32a247) - '@wagmi/core': 2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6)) + '@wagmi/connectors': 6.2.0(91b15c8e527d81870b15108fe114c24a) + '@wagmi/core': 2.22.1(@tanstack/query-core@5.104.3)(@types/react@19.2.14)(react@19.2.5)(typescript@5.9.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6)) react: 19.2.5 use-sync-external-store: 1.4.0(react@19.2.5) - viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.3.6) + viem: 2.57.7(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)(zod@4.6.6) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -18326,11 +18235,7 @@ snapshots: yocto-queue@1.2.2: {} - zod@3.22.4: {} - - zod@3.25.76: {} - - zod@4.3.6: {} + zod@4.6.6: {} zustand@5.0.0(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.4.0(react@19.2.5)): optionalDependencies: diff --git a/scripts/audit-gate.mjs b/scripts/audit-gate.mjs index 0ec3496..3529b71 100644 --- a/scripts/audit-gate.mjs +++ b/scripts/audit-gate.mjs @@ -22,7 +22,7 @@ import { pathToFileURL } from 'node:url'; // accepted, only upgraded or removed. export const MAX_CRITICAL = 0; // High findings that currently have no patched release (see the doc). -export const MAX_HIGH = 1; +export const MAX_HIGH = 0; // --------------------------------------------------------------------------- const SEVERITIES = ['critical', 'high', 'moderate', 'low', 'info']; diff --git a/scripts/dependency-compatibility.test.mjs b/scripts/dependency-compatibility.test.mjs index 932f6fc..9f7684b 100644 --- a/scripts/dependency-compatibility.test.mjs +++ b/scripts/dependency-compatibility.test.mjs @@ -3,7 +3,7 @@ // replaced with path-scoped overrides because they forced consumers onto a // version outside the major range they declare: // -// - `@stellar/stellar-sdk` declares `toml: ^3` but needs a patched major 4. +// - `@stellar/stellar-sdk` declares `toml: ^3` but needs a patched major 5. // - `metro` declares `image-size: ^1` but needs a patched major 2. // // These tests exercise the exact code path each consumer relies on with the diff --git a/src/lib/auctionEncryption.test.ts b/src/lib/auctionEncryption.test.ts new file mode 100644 index 0000000..f780e62 --- /dev/null +++ b/src/lib/auctionEncryption.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it } from 'vitest'; +import { + AUCTION_VAULT_VERSION, + decrypt, + decryptBids, + encrypt, + encryptBids, + isValidVault, + UnsupportedVersionError, + validateVault, + type EncryptedBidVault, +} from '@/lib/auctionEncryption'; + +const PASSPHRASE = 'correct-horse-battery-staple'; +const WRONG_PASSPHRASE = 'wrong-passphrase'; +const ITERATIONS = 1000; + +const bids: Record< + string, + { + name: string; + amountStroops: string; + depositStroops: string; + saltHex: string; + revealed: boolean; + } +> = { + nova: { + name: 'nova', + amountStroops: '25000000', + depositStroops: '25000000', + saltHex: 'ab'.repeat(32), + revealed: false, + }, +}; + +function tamperBase64(vault: EncryptedBidVault, index: number): EncryptedBidVault { + const data = Buffer.from(vault.data, 'base64'); + data[index] ^= 0xff; + return { ...vault, data: data.toString('base64') }; +} + +describe('auction encryption', () => { + it('encrypts and decrypts bid recovery material', async () => { + const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS); + expect(vault.version).toBe(AUCTION_VAULT_VERSION); + expect(vault.kdf.iterations).toBe(ITERATIONS); + expect(typeof vault.kdf.salt).toBe('string'); + expect(typeof vault.cipher.iv).toBe('string'); + expect(typeof vault.data).toBe('string'); + + const decrypted = await decryptBids(PASSPHRASE, vault); + expect(decrypted).toEqual(bids); + }); + + it('uses a fresh random salt and IV on every encryption', async () => { + const a = await encryptBids(PASSPHRASE, bids, ITERATIONS); + const b = await encryptBids(PASSPHRASE, bids, ITERATIONS); + expect(a.kdf.salt).not.toBe(b.kdf.salt); + expect(a.cipher.iv).not.toBe(b.cipher.iv); + expect(a.data).not.toBe(b.data); + }); + + it('rejects an incorrect passphrase', async () => { + const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS); + await expect(decryptBids(WRONG_PASSPHRASE, vault)).rejects.toThrow(); + }); + + it('detects corrupted ciphertext (authentication failure)', async () => { + const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS); + const corrupted = tamperBase64(vault, 0); + await expect(decryptBids(PASSPHRASE, corrupted)).rejects.toThrow(); + }); + + it('detects corrupted authentication tag', async () => { + const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS); + const data = Buffer.from(vault.data, 'base64'); + // Flip a byte in the final 16-byte GCM tag. + data[data.length - 1] ^= 0xff; + const tampered = { ...vault, data: data.toString('base64') }; + await expect(decryptBids(PASSPHRASE, tampered)).rejects.toThrow(); + }); + + it('rejects unsupported payload versions', async () => { + const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS); + const bad = { ...vault, version: 99 }; + expect(() => validateVault(bad)).toThrow(UnsupportedVersionError); + expect(isValidVault(bad)).toBe(false); + }); + + it('rejects invalid vault structures before decryption', async () => { + expect(() => validateVault(null)).toThrow(); + expect(() => validateVault({})).toThrow(); + expect(() => validateVault({ version: 1 })).toThrow(); + expect(isValidVault({ version: 1 })).toBe(false); + expect(isValidVault(null)).toBe(false); + expect(isValidVault(undefined)).toBe(false); + }); + + it('rejects a missing or short authentication tag', async () => { + const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS); + const noTag = { ...vault, data: Buffer.from('aa', 'hex').toString('base64') }; + expect(() => validateVault(noTag)).toThrow(); + }); + + it('never exposes the passphrase or plaintext in the vault payload', async () => { + const vault = await encryptBids(PASSPHRASE, bids, ITERATIONS); + const serialized = JSON.stringify(vault); + expect(serialized).not.toContain(PASSPHRASE); + expect(serialized).not.toContain(bids.nova.amountStroops); + expect(serialized).not.toContain(bids.nova.saltHex); + expect(serialized).not.toContain(bids.nova.depositStroops); + }); +}); diff --git a/src/lib/auctionEncryption.ts b/src/lib/auctionEncryption.ts new file mode 100644 index 0000000..e83e268 --- /dev/null +++ b/src/lib/auctionEncryption.ts @@ -0,0 +1,401 @@ +/** + * Encrypted storage format for Wraith Name auction recovery material. + * + * The auction watchlist persists sealed-bid recovery secrets (saltHex, bid + * amount, deposit amount) so a bid can be revealed after the commit phase. + * These values are sensitive: revealing them without authorisation lets an + * attacker claim the name or steal the deposit. They must never be written to + * localStorage in plaintext. + * + * Format (version 1): + * { + * version: 1, + * kdf: { algorithm: 'PBKDF2', hash: 'SHA-256', iterations: N, salt: base64 }, + * cipher: { algorithm: 'AES-GCM', iv: base64, keyLength: 256 }, + * data: base64 // AES-GCM ciphertext || 16-byte authentication tag + * } + * + * The plaintext payload is the JSON serialisation of the bids record. + * Only the encrypted payload and the non-secret metadata required to derive + * the key and verify the ciphertext are ever persisted. The passphrase, the + * derived key, and the plaintext bids are never written to localStorage. + */ + +export interface AuctionBid { + name: string; + amountStroops: string; + depositStroops: string; + saltHex: string; + revealed: boolean; + submissionStatus?: 'pending' | 'committed' | 'failed' | 'unknown'; + transactionHash?: string; +} + +export const AUCTION_VAULT_VERSION = 1; +export const AUCTION_STORAGE_KEY = 'wraith-name-auction-watchlist'; +export const LEGACY_STORAGE_KEY = 'wraith-name-auction-watchlist'; +export const LEGACY_FORMAT_VERSION = 0; + +// Production-grade PBKDF2 iteration count (OWASP 2023 baseline for +// PBKDF2-HMAC-SHA256). Tests override this via the `iterations` argument. +export const DEFAULT_PBKDF2_ITERATIONS = 600_000; + +export const SALT_LENGTH = 16; // bytes +export const IV_LENGTH = 12; // bytes (AES-GCM recommended nonce size) +export const KEY_BITS = 256; + +export interface EncryptedBidVault { + version: number; + kdf: { + algorithm: 'PBKDF2'; + hash: 'SHA-256'; + iterations: number; + salt: string; // base64 + }; + cipher: { + algorithm: 'AES-GCM'; + iv: string; // base64 + keyLength: number; // bits + }; + data: string; // base64 (ciphertext || 16-byte GCM tag) +} + +export interface BackupPayload { + type: 'wraith-name-auction-backup'; + version: 1; + exportedAt: number; + vault: EncryptedBidVault; +} + +export class AuctionEncryptionError extends Error { + constructor( + message: string, + public readonly code: + | 'invalid_vault' + | 'decrypt_failed' + | 'unsupported_version' = 'decrypt_failed', + ) { + super(message); + this.name = 'AuctionEncryptionError'; + } +} + +export class InvalidVaultError extends AuctionEncryptionError { + constructor(message: string) { + super(message, 'invalid_vault'); + } +} + +export class UnsupportedVersionError extends AuctionEncryptionError { + constructor(version: unknown) { + super(`Unsupported vault version: ${String(version)}`, 'unsupported_version'); + } +} + +function isSecureContextAvailable(): boolean { + return typeof globalThis.crypto?.subtle === 'object'; +} + +function toBase64(bytes: Uint8Array): string { + let binary = ''; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + if (typeof btoa === 'function') { + return btoa(binary); + } + // Node fallback + return Buffer.from(binary, 'binary').toString('base64'); +} + +function fromBase64(input: string): Uint8Array { + if (typeof atob === 'function') { + const binary = atob(input); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes; + } + // Node fallback + return Buffer.from(input, 'base64') as unknown as Uint8Array; +} + +function getSubtle(): SubtleCrypto { + if (!isSecureContextAvailable()) { + throw new AuctionEncryptionError('Web Crypto API is not available in this environment'); + } + return globalThis.crypto.subtle!; +} + +export function generateRandomBytes(length: number): Uint8Array { + return globalThis.crypto.getRandomValues(new Uint8Array(length)); +} + +/** + * Derive an AES-GCM encryption key from a passphrase using PBKDF2-HMAC-SHA256. + * The returned CryptoKey is non-extractable so the raw key bytes never leave + * the Web Crypto API boundary. + */ +export async function deriveKey( + passphrase: string, + salt: Uint8Array, + iterations: number = DEFAULT_PBKDF2_ITERATIONS, +): Promise { + const subtle = getSubtle(); + const keyMaterial = await subtle.importKey( + 'raw', + new TextEncoder().encode(passphrase), + { name: 'PBKDF2' }, + false, + ['deriveKey'], + ); + return subtle.deriveKey( + { name: 'PBKDF2', salt: salt as unknown as BufferSource, iterations, hash: 'SHA-256' }, + keyMaterial, + { name: 'AES-GCM', length: KEY_BITS }, + false, + ['encrypt', 'decrypt'], + ); +} + +/** + * Encrypt a plaintext string with a passphrase. Produces a fresh random salt + * (for key derivation) and a fresh random IV (for encryption) on every call. + * Returns a versioned, self-describing encrypted vault. + */ +export async function encrypt( + passphrase: string, + plaintext: string, + iterations: number = DEFAULT_PBKDF2_ITERATIONS, +): Promise { + const salt = generateRandomBytes(SALT_LENGTH); + const iv = generateRandomBytes(IV_LENGTH); + const key = await deriveKey(passphrase, salt, iterations); + const subtle = getSubtle(); + const ciphertext = await subtle.encrypt( + { name: 'AES-GCM', iv: iv as unknown as BufferSource }, + key, + new TextEncoder().encode(plaintext), + ); + return { + version: AUCTION_VAULT_VERSION, + kdf: { algorithm: 'PBKDF2', hash: 'SHA-256', iterations, salt: toBase64(salt) }, + cipher: { algorithm: 'AES-GCM', iv: toBase64(iv), keyLength: KEY_BITS }, + data: toBase64(new Uint8Array(ciphertext)), + }; +} + +/** + * Decrypt an encrypted vault with a passphrase. Validates the payload and its + * metadata before attempting decryption. Rejects on wrong passphrase or + * tampered/corrupted ciphertext (AES-GCM authentication failure). + */ +export async function decrypt(passphrase: string, vault: EncryptedBidVault): Promise { + validateVault(vault); + const salt = fromBase64(vault.kdf.salt); + const iv = fromBase64(vault.cipher.iv); + const ciphertext = new Uint8Array(fromBase64(vault.data)); + const key = await deriveKey(passphrase, salt, vault.kdf.iterations); + const subtle = getSubtle(); + let decrypted: ArrayBuffer; + try { + decrypted = await subtle.decrypt( + { name: 'AES-GCM', iv: iv as unknown as BufferSource }, + key, + ciphertext, + ); + } catch (err) { + throw new AuctionEncryptionError( + 'Unable to decrypt recovery material: incorrect passphrase or corrupted data.', + 'decrypt_failed', + ); + } + return new TextDecoder().decode(decrypted); +} + +/** Structural validation of an encrypted vault before decryption is attempted. */ +export function validateVault(vault: unknown): asserts vault is EncryptedBidVault { + if (!vault || typeof vault !== 'object') { + throw new InvalidVaultError('Vault payload is missing or not an object.'); + } + const v = vault as Record; + if (v.version !== AUCTION_VAULT_VERSION) { + throw new UnsupportedVersionError(v.version); + } + if (!v.kdf || typeof v.kdf !== 'object') { + throw new InvalidVaultError('Vault is missing key derivation metadata.'); + } + const kdf = v.kdf as Record; + if (kdf.algorithm !== 'PBKDF2' || kdf.hash !== 'SHA-256') { + throw new InvalidVaultError('Unsupported key derivation algorithm.'); + } + if ( + typeof kdf.iterations !== 'number' || + !Number.isFinite(kdf.iterations) || + kdf.iterations < 1 + ) { + throw new InvalidVaultError('Vault key derivation iteration count is invalid.'); + } + if (typeof kdf.salt !== 'string') { + throw new InvalidVaultError('Vault is missing key derivation salt.'); + } + let saltBytes: Uint8Array; + try { + saltBytes = fromBase64(kdf.salt as string); + } catch { + throw new InvalidVaultError('Vault key derivation salt is not valid base64.'); + } + if (saltBytes.length !== SALT_LENGTH) { + throw new InvalidVaultError('Vault key derivation salt has an unexpected length.'); + } + if (!v.cipher || typeof v.cipher !== 'object') { + throw new InvalidVaultError('Vault is missing cipher metadata.'); + } + const cipher = v.cipher as Record; + if (cipher.algorithm !== 'AES-GCM') { + throw new InvalidVaultError('Unsupported cipher algorithm.'); + } + if (typeof cipher.keyLength !== 'number' || cipher.keyLength !== KEY_BITS) { + throw new InvalidVaultError('Vault cipher key length is invalid.'); + } + if (typeof cipher.iv !== 'string') { + throw new InvalidVaultError('Vault is missing encryption IV.'); + } + let ivBytes: Uint8Array; + try { + ivBytes = fromBase64(cipher.iv as string); + } catch { + throw new InvalidVaultError('Vault encryption IV is not valid base64.'); + } + if (ivBytes.length !== IV_LENGTH) { + throw new InvalidVaultError('Vault encryption IV has an unexpected length.'); + } + if (typeof v.data !== 'string') { + throw new InvalidVaultError('Vault is missing encrypted data.'); + } + let dataBytes: Uint8Array; + try { + dataBytes = fromBase64(v.data as string); + } catch { + throw new InvalidVaultError('Vault encrypted data is not valid base64.'); + } + // AES-GCM ciphertext must be at least the 16-byte authentication tag. + if (dataBytes.length < 16) { + throw new InvalidVaultError( + 'Vault encrypted data is too short to contain an authentication tag.', + ); + } +} + +export function isValidVault(vault: unknown): vault is EncryptedBidVault { + try { + validateVault(vault); + return true; + } catch { + return false; + } +} + +/** + * Encrypt the bid recovery map with a passphrase. The plaintext payload is the + * JSON serialisation of the bids record. + */ +export async function encryptBids( + passphrase: string, + bids: Record, + iterations: number = DEFAULT_PBKDF2_ITERATIONS, +): Promise { + return encrypt(passphrase, JSON.stringify(bids), iterations); +} + +/** + * Decrypt an encrypted vault into the bid recovery map. Throws + * AuctionEncryptionError on wrong passphrase, corruption, or invalid payload, + * and a plain Error if the decrypted payload is not a valid bids record. + */ +export async function decryptBids( + passphrase: string, + vault: EncryptedBidVault, +): Promise> { + const plaintext = await decrypt(passphrase, vault); + let parsed: unknown; + try { + parsed = JSON.parse(plaintext); + } catch { + throw new AuctionEncryptionError( + 'Decrypted recovery material is not valid JSON.', + 'decrypt_failed', + ); + } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new AuctionEncryptionError( + 'Decrypted recovery material is not a bids record.', + 'decrypt_failed', + ); + } + const bids = parsed as Record; + const invalidKeys: string[] = []; + for (const [name, value] of Object.entries(bids)) { + if (!isAuctionBid(value)) { + invalidKeys.push(name); + } + } + if (invalidKeys.length > 0) { + throw new AuctionEncryptionError( + `Decrypted recovery material contains malformed bid(s): ${invalidKeys.join(', ')}.`, + 'decrypt_failed', + ); + } + return bids as Record; +} + +/** Build a documented, versioned backup payload suitable for export and later recovery. */ +export function createBackup(vault: EncryptedBidVault): BackupPayload { + validateVault(vault); + return { + type: 'wraith-name-auction-backup', + version: 1, + exportedAt: Date.now(), + vault, + }; +} + +/** Parse and validate a backup payload (wrapper or raw vault). */ +export function parseBackup(input: unknown): BackupPayload { + if (!input || typeof input !== 'object') { + throw new InvalidVaultError('Backup payload is missing or not an object.'); + } + const p = input as Record; + if (p.type === 'wraith-name-auction-backup' && p.vault) { + const vault = p.vault as EncryptedBidVault; + validateVault(vault); + return { + type: 'wraith-name-auction-backup', + version: 1, + exportedAt: typeof p.exportedAt === 'number' ? p.exportedAt : Date.now(), + vault, + }; + } + // Backward-compatible: raw vault. + validateVault(p); + return { + type: 'wraith-name-auction-backup', + version: 1, + exportedAt: Date.now(), + vault: p as EncryptedBidVault, + }; +} + +export function isAuctionBid(value: unknown): value is AuctionBid { + if (!value || typeof value !== 'object') return false; + const b = value as Record; + return ( + typeof b.name === 'string' && + typeof b.amountStroops === 'string' && + typeof b.depositStroops === 'string' && + typeof b.saltHex === 'string' && + typeof b.revealed === 'boolean' && + (b.submissionStatus === undefined || + ['pending', 'committed', 'failed', 'unknown'].includes(String(b.submissionStatus))) && + (b.transactionHash === undefined || typeof b.transactionHash === 'string') + ); +} diff --git a/src/lib/stellar/auctionBidReconciliation.test.ts b/src/lib/stellar/auctionBidReconciliation.test.ts new file mode 100644 index 0000000..209c701 --- /dev/null +++ b/src/lib/stellar/auctionBidReconciliation.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { AuctionBid } from '@/lib/auctionEncryption'; +import { + reconcileUnknownBid, + reconcileUnknownBids, + type TransactionStatusLookup, +} from './auctionBidReconciliation'; + +const bid: AuctionBid = { + name: 'nova', + amountStroops: '25000000', + depositStroops: '25000000', + saltHex: 'ab'.repeat(32), + revealed: false, + submissionStatus: 'unknown', + transactionHash: 'tx-hash', +}; + +describe('auction bid transaction reconciliation', () => { + it('promotes an unknown bid to committed after an authoritative success', async () => { + const lookup = vi.fn().mockResolvedValue('SUCCESS'); + const update = vi.fn().mockResolvedValue(undefined); + + await expect(reconcileUnknownBid(bid, lookup, update)).resolves.toBe('committed'); + expect(lookup).toHaveBeenCalledWith('tx-hash'); + expect(update).toHaveBeenCalledWith('nova', 'committed', 'tx-hash'); + }); + + it('moves an unknown bid to failed only after an authoritative failure', async () => { + const lookup = vi.fn().mockResolvedValue('FAILED'); + const update = vi.fn().mockResolvedValue(undefined); + + await expect(reconcileUnknownBid(bid, lookup, update)).resolves.toBe('failed'); + expect(update).toHaveBeenCalledWith('nova', 'failed'); + }); + + it('preserves unknown when RPC cannot find the transaction yet', async () => { + const lookup = vi.fn().mockResolvedValue('NOT_FOUND'); + const update = vi.fn().mockResolvedValue(undefined); + + await expect(reconcileUnknownBid(bid, lookup, update)).resolves.toBe('unknown'); + expect(update).not.toHaveBeenCalled(); + }); + + it('preserves unknown when the network lookup fails', async () => { + const lookup = vi.fn().mockRejectedValue(new Error('offline')); + const update = vi.fn().mockResolvedValue(undefined); + + await expect(reconcileUnknownBid(bid, lookup, update)).resolves.toBe('unknown'); + expect(update).not.toHaveBeenCalled(); + }); + + it('resumes after reload from the persisted hash and reconciles eligible records', async () => { + const rehydrated = JSON.parse(JSON.stringify({ nova: bid })) as Record; + const lookup = vi.fn().mockResolvedValue('SUCCESS'); + const update = vi.fn().mockResolvedValue(undefined); + + await reconcileUnknownBids(rehydrated, lookup, update); + + expect(lookup).toHaveBeenCalledWith('tx-hash'); + expect(update).toHaveBeenCalledWith('nova', 'committed', 'tx-hash'); + }); + + it('does not reconcile committed, failed, or hashless pending records', async () => { + const lookup = vi.fn().mockResolvedValue('SUCCESS'); + const update = vi.fn().mockResolvedValue(undefined); + await reconcileUnknownBids( + { + committed: { ...bid, name: 'committed', submissionStatus: 'committed' }, + failed: { ...bid, name: 'failed', submissionStatus: 'failed' }, + pending: { + ...bid, + name: 'pending', + submissionStatus: 'pending', + transactionHash: undefined, + }, + }, + lookup, + update, + ); + + expect(lookup).not.toHaveBeenCalled(); + expect(update).not.toHaveBeenCalled(); + }); +}); diff --git a/src/lib/stellar/auctionBidReconciliation.ts b/src/lib/stellar/auctionBidReconciliation.ts new file mode 100644 index 0000000..6106f09 --- /dev/null +++ b/src/lib/stellar/auctionBidReconciliation.ts @@ -0,0 +1,58 @@ +import type { AuctionBid } from '@/lib/auctionEncryption'; + +export type AuthoritativeTransactionStatus = 'SUCCESS' | 'FAILED' | 'NOT_FOUND'; + +export type TransactionStatusLookup = (hash: string) => Promise; + +export type BidSubmissionUpdater = ( + name: string, + status: NonNullable, + transactionHash?: string, +) => Promise; + +/** + * Resolve saved ambiguous submissions only from an authoritative Soroban + * transaction result. NOT_FOUND is deliberately inconclusive: RPC indexing, + * retention, or propagation delays must never make a retry appear safe. + */ +export async function reconcileUnknownBid( + bid: AuctionBid, + lookup: TransactionStatusLookup, + update: BidSubmissionUpdater, +): Promise<'committed' | 'failed' | 'unknown' | 'skipped'> { + if ( + (bid.submissionStatus !== 'unknown' && bid.submissionStatus !== 'pending') || + !bid.transactionHash + ) { + return 'skipped'; + } + + let status: AuthoritativeTransactionStatus; + try { + status = await lookup(bid.transactionHash); + } catch { + return 'unknown'; + } + + if (status === 'SUCCESS') { + await update(bid.name, 'committed', bid.transactionHash); + return 'committed'; + } + + if (status === 'FAILED') { + await update(bid.name, 'failed'); + return 'failed'; + } + + return 'unknown'; +} + +export async function reconcileUnknownBids( + bids: Record, + lookup: TransactionStatusLookup, + update: BidSubmissionUpdater, +): Promise { + for (const bid of Object.values(bids)) { + await reconcileUnknownBid(bid, lookup, update); + } +} diff --git a/src/lib/stellar/auctionBidSubmission.test.ts b/src/lib/stellar/auctionBidSubmission.test.ts new file mode 100644 index 0000000..14dcb99 --- /dev/null +++ b/src/lib/stellar/auctionBidSubmission.test.ts @@ -0,0 +1,165 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { AuctionBid } from '@/lib/auctionEncryption'; +import { submitBidWithRecovery, TransactionSubmissionError } from './auctionBidSubmission'; + +const bid: AuctionBid = { + name: 'nova', + amountStroops: '25000000', + depositStroops: '25000000', + saltHex: 'ab'.repeat(32), + revealed: false, +}; + +function setup() { + let storedBid: AuctionBid = bid; + const saveBid = vi.fn(async (nextBid: AuctionBid) => { + storedBid = nextBid; + }); + const updateBidSubmission = vi.fn( + async ( + _name: string, + status: NonNullable, + transactionHash?: string, + ) => { + storedBid = { ...storedBid, submissionStatus: status, transactionHash }; + }, + ); + return { + saveBid, + updateBidSubmission, + getStoredBid: () => storedBid, + }; +} + +describe('auction bid submission recovery', () => { + it('stages the encrypted secret before signing and makes wallet rejection retryable', async () => { + const state = setup(); + const calls: string[] = []; + const signTransaction = vi.fn(async () => { + calls.push('sign'); + throw new Error('User rejected request'); + }); + const saveBid = vi.fn(async (nextBid: AuctionBid) => { + calls.push('save'); + state.saveBid(nextBid); + }); + const submitTransaction = vi.fn(); + + const result = await submitBidWithRecovery(bid, { + ...state, + saveBid, + buildTransaction: vi.fn().mockResolvedValue('unsigned-xdr'), + signTransaction, + submitTransaction, + }); + + expect(result.status).toBe('failed'); + expect(calls).toEqual(['save', 'sign']); + expect(saveBid).toHaveBeenCalledWith({ ...bid, submissionStatus: 'pending' }); + expect(state.getStoredBid()).toMatchObject({ + saltHex: bid.saltHex, + submissionStatus: 'failed', + }); + expect(submitTransaction).not.toHaveBeenCalled(); + }); + + it('marks a confirmed pre-broadcast rejection failed and preserves the same secret', async () => { + const state = setup(); + + const result = await submitBidWithRecovery(bid, { + ...state, + buildTransaction: vi.fn().mockResolvedValue('unsigned-xdr'), + signTransaction: vi.fn().mockResolvedValue('signed-xdr'), + submitTransaction: vi + .fn() + .mockRejectedValue(new TransactionSubmissionError('Transaction rejected', 'rejected')), + }); + + expect(result.status).toBe('failed'); + expect(state.getStoredBid()).toMatchObject({ + saltHex: bid.saltHex, + amountStroops: bid.amountStroops, + submissionStatus: 'failed', + }); + }); + + it('keeps ambiguous post-broadcast failures non-retryable', async () => { + const state = setup(); + + const result = await submitBidWithRecovery(bid, { + ...state, + buildTransaction: vi.fn().mockResolvedValue('unsigned-xdr'), + signTransaction: vi.fn().mockResolvedValue('signed-xdr'), + submitTransaction: vi + .fn() + .mockRejectedValue( + new TransactionSubmissionError('Connection timed out', 'unknown', 'possible-hash'), + ), + }); + + expect(result.status).toBe('unknown'); + expect(result.transactionHash).toBe('possible-hash'); + expect(state.getStoredBid()).toMatchObject({ + saltHex: bid.saltHex, + submissionStatus: 'unknown', + transactionHash: 'possible-hash', + }); + }); + + it('persists the transaction hash before broadcast for reload-safe reconciliation', async () => { + const state = setup(); + const calls: string[] = []; + const submitTransaction = vi.fn( + async (_xdr: string, onPrepared?: (hash: string) => Promise) => { + await onPrepared?.('prepared-hash'); + calls.push('broadcast'); + return 'prepared-hash'; + }, + ); + const updateBidSubmission = vi.fn( + async ( + name: string, + status: NonNullable, + transactionHash?: string, + ) => { + calls.push(`persist:${status}:${transactionHash ?? ''}`); + await state.updateBidSubmission(name, status, transactionHash); + }, + ); + + const result = await submitBidWithRecovery(bid, { + ...state, + updateBidSubmission, + buildTransaction: vi.fn().mockResolvedValue('unsigned-xdr'), + signTransaction: vi.fn().mockResolvedValue('signed-xdr'), + submitTransaction, + }); + + expect(result.status).toBe('unknown'); + expect(calls).toEqual([ + 'persist:pending:prepared-hash', + 'broadcast', + 'persist:unknown:prepared-hash', + ]); + expect(updateBidSubmission).toHaveBeenCalledWith('nova', 'unknown', 'prepared-hash'); + }); + + it('does not mark a submitted transaction committed before Soroban confirms execution', async () => { + const state = setup(); + + const result = await submitBidWithRecovery(bid, { + ...state, + buildTransaction: vi.fn().mockResolvedValue('unsigned-xdr'), + signTransaction: vi.fn().mockResolvedValue('signed-xdr'), + submitTransaction: vi.fn().mockResolvedValue('transaction-hash'), + }); + + expect(result.status).toBe('unknown'); + expect(result.transactionHash).toBe('transaction-hash'); + expect(state.getStoredBid()).toMatchObject({ + saltHex: bid.saltHex, + submissionStatus: 'unknown', + transactionHash: 'transaction-hash', + }); + }); +}); diff --git a/src/lib/stellar/auctionBidSubmission.ts b/src/lib/stellar/auctionBidSubmission.ts new file mode 100644 index 0000000..7ff8105 --- /dev/null +++ b/src/lib/stellar/auctionBidSubmission.ts @@ -0,0 +1,78 @@ +import type { AuctionBid } from '@/lib/auctionEncryption'; + +export class TransactionSubmissionError extends Error { + constructor( + message: string, + readonly outcome: 'rejected' | 'unknown', + readonly transactionHash?: string, + ) { + super(message); + this.name = 'TransactionSubmissionError'; + } +} + +export type BidSubmissionStatus = NonNullable; + +interface SubmitBidOptions { + saveBid: (bid: AuctionBid) => Promise; + updateBidSubmission: ( + name: string, + status: BidSubmissionStatus, + transactionHash?: string, + ) => Promise; + buildTransaction: () => Promise; + signTransaction: (xdr: string) => Promise; + submitTransaction: ( + signedXdr: string, + onPrepared?: (transactionHash: string) => Promise, + ) => Promise; +} + +export type BidSubmissionResult = + | { status: 'committed'; transactionHash: string } + | { status: 'failed' | 'unknown'; error: unknown; transactionHash?: string }; + +export async function submitBidWithRecovery( + bid: AuctionBid, + options: SubmitBidOptions, +): Promise { + const stagedBid: AuctionBid = { ...bid, submissionStatus: 'pending' }; + delete stagedBid.transactionHash; + await options.saveBid(stagedBid); + + let signedXdr: string; + try { + signedXdr = await options.signTransaction(await options.buildTransaction()); + } catch (error) { + await options.updateBidSubmission(bid.name, 'failed'); + return { status: 'failed', error }; + } + + let transactionHash: string; + try { + transactionHash = await options.submitTransaction(signedXdr, async (hash) => { + // Persist the deterministic hash before broadcast so a reload between + // submission and confirmation can still reconcile this exact transaction. + await options.updateBidSubmission(bid.name, 'pending', hash); + }); + } catch (error) { + const status = + error instanceof TransactionSubmissionError && error.outcome === 'rejected' + ? 'failed' + : 'unknown'; + const transactionHash = + error instanceof TransactionSubmissionError ? error.transactionHash : undefined; + await options.updateBidSubmission(bid.name, status, transactionHash); + return { status, error, ...(transactionHash ? { transactionHash } : {}) }; + } + + // sendTransaction returning a hash only confirms that the RPC accepted the + // submission. Soroban execution may still be pending or may fail, so keep + // the persisted bid non-committed until getTransaction confirms SUCCESS. + await options.updateBidSubmission(bid.name, 'unknown', transactionHash); + return { + status: 'unknown', + transactionHash, + error: new Error('Transaction submitted; awaiting authoritative confirmation.'), + }; +} diff --git a/src/lib/stellar/names.ts b/src/lib/stellar/names.ts index 957512a..9700566 100644 --- a/src/lib/stellar/names.ts +++ b/src/lib/stellar/names.ts @@ -9,6 +9,7 @@ import { } from '@stellar/stellar-sdk'; import { Buffer } from 'buffer'; import { STELLAR_NETWORK } from '@/config'; +import { TransactionSubmissionError } from '@/lib/stellar/auctionBidSubmission'; import { getStellarContractId } from './deployments'; /** @@ -708,16 +709,73 @@ export async function getOwnedNames(ownerAddress: string): Promise { /** * Submit a signed transaction to the network */ -export async function submitTransaction(signedXdr: string): Promise { - const { rpc } = await import('@stellar/stellar-sdk'); - const server = new rpc.Server(STELLAR_NETWORK.rpcUrl); +export async function submitTransaction( + signedXdr: string, + onPrepared?: (transactionHash: string) => Promise, +): Promise { + let server: InstanceType<(typeof import('@stellar/stellar-sdk'))['rpc']['Server']>; + let tx: ReturnType; + let transactionHash: string; + try { + const { rpc } = await import('@stellar/stellar-sdk'); + server = new rpc.Server(STELLAR_NETWORK.rpcUrl); + tx = TransactionBuilder.fromXDR(signedXdr, STELLAR_NETWORK.networkPassphrase); + transactionHash = tx.hash().toString('hex'); + } catch (error) { + throw new TransactionSubmissionError( + error instanceof Error ? error.message : 'Failed to prepare transaction submission.', + 'rejected', + ); + } + + if (onPrepared) { + try { + await onPrepared(transactionHash); + } catch (error) { + throw new TransactionSubmissionError( + error instanceof Error + ? `Could not persist transaction recovery state: ${error.message}` + : 'Could not persist transaction recovery state.', + 'rejected', + ); + } + } - const tx = TransactionBuilder.fromXDR(signedXdr, STELLAR_NETWORK.networkPassphrase); - const result = await server.sendTransaction(tx); + let result: Awaited>; + try { + result = await server.sendTransaction(tx); + } catch (error) { + throw new TransactionSubmissionError( + error instanceof Error ? error.message : 'Transaction submission outcome is unknown.', + 'unknown', + transactionHash, + ); + } - if (result.status === 'ERROR') { - throw new Error(result.errorResult?.toString() || 'Transaction failed'); + if (result.status === 'ERROR' || result.status === 'TRY_AGAIN_LATER') { + throw new TransactionSubmissionError( + result.errorResult?.toString() || + (result.status === 'TRY_AGAIN_LATER' + ? 'The network did not accept the transaction; retry is safe.' + : 'Transaction was rejected before broadcast.'), + 'rejected', + ); } return result.hash; } + +/** + * Query Soroban RPC for an authoritative transaction result. NOT_FOUND is + * inconclusive and callers must leave the bid in its unknown state. + */ +export async function getNameBidTransactionStatus( + transactionHash: string, +): Promise<'SUCCESS' | 'FAILED' | 'NOT_FOUND'> { + const { rpc } = await import('@stellar/stellar-sdk'); + const server = new rpc.Server(STELLAR_NETWORK.rpcUrl); + const result = await server.getTransaction(transactionHash); + if (result.status === 'SUCCESS') return 'SUCCESS'; + if (result.status === 'FAILED') return 'FAILED'; + return 'NOT_FOUND'; +} diff --git a/src/pages/NamesAuctions.tsx b/src/pages/NamesAuctions.tsx index e3f70df..a13a1c4 100644 --- a/src/pages/NamesAuctions.tsx +++ b/src/pages/NamesAuctions.tsx @@ -1,12 +1,14 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { decodeStealthMetaAddress } from '@wraith-protocol/sdk/chains/stellar'; import { useStellarWallet } from '@/context/StellarWalletContext'; import { useStealthKeys } from '@/context/StealthKeysContext'; import { useContacts } from '@/store/contactsStore'; import { useNameHistory } from '@/store/nameHistoryStore'; -import { useNameWatchlistStore } from '@/store/nameWatchlistStore'; +import { useNameWatchlistStore, type AuctionBid } from '@/store/nameWatchlistStore'; import { stellarTxUrl } from '@/lib/explorer'; +import { submitBidWithRecovery } from '@/lib/stellar/auctionBidSubmission'; +import { reconcileUnknownBids } from '@/lib/stellar/auctionBidReconciliation'; import { tryGetStellarContractId } from '@/lib/stellar/deployments'; import { MIN_AUCTION_BID_INCREMENT, @@ -20,12 +22,11 @@ import { getNameAuction, getNameAuctionConfig, submitTransaction, + getNameBidTransactionStatus, type NameAuction, type NameAuctionConfig, } from '@/lib/stellar/names'; -// Display-only label lookup: an undisployed names contract resolves to an empty -// address so the page still renders, while every auction call fails closed. const NAMES_CONTRACT_ID = tryGetStellarContractId('names'); const STROOPS_PER_XLM = 10_000_000n; @@ -42,6 +43,13 @@ function formatXlm(stroops: bigint) { return fraction ? `${whole}.${fraction} XLM` : `${whole} XLM`; } +function formatXlmInput(stroops: string) { + const amount = BigInt(stroops); + const whole = amount / STROOPS_PER_XLM; + const fraction = (amount % STROOPS_PER_XLM).toString().padStart(7, '0').replace(/0+$/, ''); + return fraction ? `${whole}.${fraction}` : whole.toString(); +} + function formatEndsAt(timestamp: number) { return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', @@ -61,14 +69,27 @@ export default function NamesAuctions() { const { stellarMetaAddress } = useStealthKeys(); const { isKnownAddress } = useContacts(); const { isKnownRecipient } = useNameHistory(); + const watchedAuctions = useNameWatchlistStore((state) => state.watchedAuctions); const bids = useNameWatchlistStore((state) => state.bids); const watchAuction = useNameWatchlistStore((state) => state.watchAuction); const unwatchAuction = useNameWatchlistStore((state) => state.unwatchAuction); const saveBid = useNameWatchlistStore((state) => state.saveBid); + const updateBidSubmission = useNameWatchlistStore((state) => state.updateBidSubmission); const markBidRevealed = useNameWatchlistStore((state) => state.markBidRevealed); const removeBid = useNameWatchlistStore((state) => state.removeBid); + const status = useNameWatchlistStore((state) => state.status); + const hasLegacyPlaintext = useNameWatchlistStore((state) => state.hasLegacyPlaintext); + const hasEncryptedVault = useNameWatchlistStore((state) => state.hasEncryptedVault); + const inspectStorage = useNameWatchlistStore((state) => state.inspectStorage); + const setPassphrase = useNameWatchlistStore((state) => state.setPassphrase); + const unlock = useNameWatchlistStore((state) => state.unlock); + const lock = useNameWatchlistStore((state) => state.lock); + const exportBackup = useNameWatchlistStore((state) => state.exportBackup); + const importBackup = useNameWatchlistStore((state) => state.importBackup); + const clearLegacy = useNameWatchlistStore((state) => state.clearLegacy); + const [auctions, setAuctions] = useState([]); const [config, setConfig] = useState(null); const [now, setNow] = useState(() => Math.floor(Date.now() / 1000)); @@ -80,6 +101,16 @@ export default function NamesAuctions() { const [selectedAuction, setSelectedAuction] = useState(null); const [bidAmount, setBidAmount] = useState(''); + // Vault UI state + const [vaultError, setVaultError] = useState(''); + const [passphrase, setPassphraseValue] = useState(''); + const [confirmPassphrase, setConfirmPassphrase] = useState(''); + const [importing, setImporting] = useState(false); + const [importFile, setImportFile] = useState(null); + const [revealPending, setRevealPending] = useState(null); + const [backupReady, setBackupReady] = useState(null); + const importFileInput = useRef(null); + const trackedNames = useMemo( () => [...new Set([...watchedAuctions.map((item) => item.name), ...Object.keys(bids)])], [bids, watchedAuctions], @@ -87,6 +118,7 @@ export default function NamesAuctions() { const trackedNamesKey = trackedNames.join('|'); const isUnknownContract = !isKnownAddress(NAMES_CONTRACT_ID) && !isKnownRecipient(NAMES_CONTRACT_ID); + const unlocked = status === 'unlocked'; const refreshAuctions = useCallback(async () => { setIsLoading(true); @@ -105,6 +137,10 @@ export default function NamesAuctions() { } }, [trackedNamesKey]); + useEffect(() => { + inspectStorage(); + }, [inspectStorage]); + useEffect(() => { void refreshAuctions(); }, [refreshAuctions]); @@ -114,6 +150,33 @@ export default function NamesAuctions() { return () => globalThis.clearInterval(timer); }, []); + // Reconcile ambiguous submissions after unlock/reload and retry periodically. + // Only authoritative Soroban SUCCESS/FAILED results change persisted state. + useEffect(() => { + if (!unlocked) return; + let active = true; + const reconcile = async () => { + try { + await reconcileUnknownBids( + bids, + getNameBidTransactionStatus, + async (name, nextStatus, hash) => { + if (active) await updateBidSubmission(name, nextStatus, hash); + }, + ); + } catch { + // Keep unknown/pending state on RPC or persistence errors; the next + // interval retries without enabling reveal, refund, or another bid. + } + }; + void reconcile(); + const timer = globalThis.setInterval(() => void reconcile(), 15_000); + return () => { + active = false; + globalThis.clearInterval(timer); + }; + }, [bids, unlocked, updateBidSubmission]); + const trackAuction = async () => { const name = lookupName .trim() @@ -168,43 +231,92 @@ export default function NamesAuctions() { : parsedBid !== null && parsedBid < minimumBid ? `Minimum bid is ${formatXlm(minimumBid)}.` : ''; + const selectedBid = selectedAuction ? bids[selectedAuction.name] : undefined; + const retryingBid = selectedBid?.submissionStatus === 'failed'; + const selectedBidCannotRetry = + selectedBid?.submissionStatus === 'pending' || selectedBid?.submissionStatus === 'unknown'; const placeBid = async () => { + if (!unlocked) { + setError('Set a passphrase to secure your bid recovery material before placing a bid.'); + return; + } if (!address || !selectedAuction || parsedBid === null || bidError) return; const action = `bid:${selectedAuction.name}`; setPendingAction(action); setError(''); + setTxHash(''); try { - const salt = globalThis.crypto.getRandomValues(new Uint8Array(32)); - const saltHex = Array.from(salt, (byte) => byte.toString(16).padStart(2, '0')).join(''); + const existingBid = bids[selectedAuction.name]; + if (existingBid && existingBid.submissionStatus !== 'failed') { + throw new Error('This bid cannot be retried until its submission status is confirmed.'); + } + if ( + existingBid && + existingBid.submissionStatus === 'failed' && + parsedBid.toString() !== existingBid.amountStroops + ) { + throw new Error('A retry must use the original bid amount and recovery secret.'); + } + const saltHex = + existingBid?.submissionStatus === 'failed' + ? existingBid.saltHex + : Array.from(globalThis.crypto.getRandomValues(new Uint8Array(32)), (byte) => + byte.toString(16).padStart(2, '0'), + ).join(''); + const bid: AuctionBid = + existingBid?.submissionStatus === 'failed' + ? existingBid + : { + name: selectedAuction.name, + amountStroops: parsedBid.toString(), + depositStroops: parsedBid.toString(), + saltHex, + revealed: false, + }; const commitmentHex = await computeNameBidCommitment( address, selectedAuction.name, - parsedBid, + BigInt(bid.amountStroops), saltHex, ); - const submitted = await submitAuctionTransaction(action, () => - buildCommitNameBidTransaction(address, { - name: selectedAuction.name, - commitmentHex, - deposit: parsedBid, - }), - ); - if (submitted) { - saveBid({ - name: selectedAuction.name, - amountStroops: parsedBid.toString(), - depositStroops: parsedBid.toString(), - saltHex, - revealed: false, - }); + const result = await submitBidWithRecovery(bid, { + saveBid, + updateBidSubmission, + buildTransaction: () => + buildCommitNameBidTransaction(address, { + name: selectedAuction.name, + commitmentHex, + deposit: BigInt(bid.depositStroops), + }), + signTransaction, + submitTransaction, + }); + if (result.status === 'committed') { + setTxHash(result.transactionHash); watchAuction({ name: selectedAuction.name, endsAt: selectedAuction.revealEnd }); setSelectedAuction(null); setBidAmount(''); + await refreshAuctions(); + } else if (result.status === 'failed') { + const message = result.error instanceof Error ? result.error.message : 'Transaction failed'; + setError( + `Bid was not submitted: ${message} Your encrypted secret is saved; retry is safe.`, + ); + } else { + const message = + result.error instanceof Error + ? result.error.message + : 'The submission outcome is unknown.'; + setError( + `${message} Your encrypted secret is preserved. Do not retry until you confirm the transaction was not submitted.`, + ); } } catch (commitmentError) { setError( - commitmentError instanceof Error ? commitmentError.message : 'Failed to prepare bid', + commitmentError instanceof Error + ? commitmentError.message + : 'Failed to prepare or persist bid recovery material.', ); } finally { setPendingAction(''); @@ -212,9 +324,15 @@ export default function NamesAuctions() { }; const revealBid = async (auction: NameAuction) => { + if (!unlocked) { + setRevealPending(auction); + setError('Unlock your vault with your passphrase before revealing a bid.'); + return; + } if (!address) return; const bid = bids[auction.name]; if (!bid) return; + setRevealPending(auction); const submitted = await submitAuctionTransaction(`reveal:${auction.name}`, () => buildRevealNameBidTransaction(address, { name: auction.name, @@ -222,7 +340,14 @@ export default function NamesAuctions() { saltHex: bid.saltHex, }), ); - if (submitted) markBidRevealed(auction.name); + setRevealPending(null); + if (submitted) { + try { + await markBidRevealed(auction.name); + } catch { + // reveal transaction submitted; marking revealed is best-effort local state + } + } }; const settleAuction = async (auction: NameAuction) => { @@ -244,7 +369,13 @@ export default function NamesAuctions() { const submitted = await submitAuctionTransaction(`claim:${auction.name}`, () => buildClaimAuctionNameTransaction(address, auction.name, metaAddress), ); - if (submitted) removeBid(auction.name); + if (submitted) { + try { + await removeBid(auction.name); + } catch { + // claim submitted; local bid removal is best-effort + } + } }; const refundBid = async (auction: NameAuction) => { @@ -252,9 +383,124 @@ export default function NamesAuctions() { const submitted = await submitAuctionTransaction(`refund:${auction.name}`, () => buildRefundNameBidTransaction(address, auction.name), ); - if (submitted) removeBid(auction.name); + if (submitted) { + try { + await removeBid(auction.name); + } catch { + // refund submitted; local bid removal is best-effort + } + } + }; + + // Vault actions + const handleSetPassphrase = async () => { + setVaultError(''); + try { + if (passphrase !== confirmPassphrase) { + throw new Error('Passphrases do not match.'); + } + if (status === 'legacy') { + await setPassphrase(passphrase); // migrates legacy plaintext + } else { + await setPassphrase(passphrase); // creates new vault + } + setPassphraseValue(''); + setConfirmPassphrase(''); + } catch (err) { + setVaultError(err instanceof Error ? err.message : 'Failed to set up encryption.'); + } + }; + + const handleUnlock = async () => { + setVaultError(''); + try { + await unlock(passphrase); + setPassphraseValue(''); + } catch (err) { + setVaultError(err instanceof Error ? err.message : 'Unable to unlock vault.'); + } + }; + + const handleBackup = async () => { + setVaultError(''); + setBackupReady(null); + try { + const json = await exportBackup(); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `wraith-name-auction-backup-${Date.now()}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + setBackupReady(json); + } catch (err) { + setVaultError(err instanceof Error ? err.message : 'Backup failed.'); + } }; + const handleImportFile = async (file: File) => { + setVaultError(''); + setImporting(true); + try { + const text = await file.text(); + // Never silently replace a non-empty current vault. Require explicit + // confirmation before importing overwrites the current recovery secrets. + let replace = false; + if (hasEncryptedVault) { + const confirmed = window.confirm( + 'Your current vault already contains bid recovery secrets. Importing this backup will replace it. Export your current vault first, or click OK to confirm replacement.', + ); + if (!confirmed) { + setImporting(false); + return; + } + replace = true; + } + await importBackup(text, passphrase, { replace }); + setPassphraseValue(''); + setImportFile(null); + } catch (err) { + setVaultError(err instanceof Error ? err.message : 'Import failed.'); + } finally { + setImporting(false); + } + }; + + const handleClearLegacy = async () => { + setVaultError(''); + if (!hasLegacyPlaintext) return; + if ( + !confirm( + 'This will permanently delete the unencrypted legacy bid recovery material from localStorage. The encrypted vault must already exist. Continue?', + ) + ) { + return; + } + try { + const cleared = await clearLegacy(); + if (!cleared) { + setVaultError('No legacy plaintext found to clear.'); + } + } catch (err) { + setVaultError(err instanceof Error ? err.message : 'Failed to clear legacy plaintext.'); + } + }; + + const bidCount = Object.keys(bids).length; + const statusLabel = + status === 'empty' + ? 'No vault' + : status === 'legacy' + ? 'Legacy plaintext (unmigrated)' + : status === 'locked' + ? 'Vault locked' + : status === 'unlocked' + ? 'Vault unlocked' + : 'Error'; + if (!isConnected) { return (
@@ -285,10 +531,162 @@ export default function NamesAuctions() {

Bid privately during the commit phase, reveal your bid, then claim the name or refund your - deposit after the auction. + deposit after the auction. Bid recovery material (salt, amount, deposit) is encrypted with + your passphrase and never written to localStorage in plaintext.

+ {/* Recovery vault controls */} +
+
+
+ + Recovery vault + + + [{statusLabel}] + +
+ {status === 'unlocked' && ( + + )} +
+ + {vaultError && ( +

+ {vaultError} +

+ )} + + {status === 'empty' || status === 'legacy' || status === 'locked' ? ( +
+
+ + setPassphraseValue(e.target.value)} + placeholder="••••••••" + className="h-10 w-full border border-outline-variant bg-surface px-3 font-mono text-sm text-primary focus:border-primary" + /> +
+ {status !== 'empty' && ( +
+ + setConfirmPassphrase(e.target.value)} + placeholder="••••••••" + className="h-10 w-full border border-outline-variant bg-surface px-3 font-mono text-sm text-primary focus:border-primary" + /> +
+ )} + +
+ ) : null} + + {status === 'unlocked' && ( +
+

+ {bidCount} bid recovery secret(s) held in memory only. +

+ + { + const file = e.target.files?.[0] ?? null; + setImportFile(file); + if (file) void handleImportFile(file); + }} + /> + + {hasLegacyPlaintext && ( + + )} + {backupReady && ( +

+ Backup exported (encrypted, passphrase required to restore). +

+ )} +
+ )} +
+ + {error && ( +

+ {error} +

+ )} + {txHash && ( +

+ Transaction submitted.{' '} + + View transaction + +

+ )} +
- {error && ( -

- {error} -

- )} - {txHash && ( -

- Transaction submitted.{' '} - - View transaction - + {revealPending && ( +

+ Ready to reveal {revealPending.name}.wraith. Unlock the vault above, then select Reveal + bid again.

)} @@ -343,6 +729,8 @@ export default function NamesAuctions() { auctions.map((auction) => { const phase = phaseFor(auction, now); const bid = bids[auction.name]; + const bidStatus = bid?.submissionStatus ?? 'committed'; + const hasCommittedBid = Boolean(bid && bidStatus === 'committed'); const isWinner = auction.highestBidder === address; const isWatched = watchedAuctions.some((item) => item.name === auction.name); return ( @@ -395,11 +783,43 @@ export default function NamesAuctions() { Your bid
- {bid ? formatXlm(BigInt(bid.amountStroops)) : '—'} + {hasCommittedBid + ? formatXlm(BigInt(bid.amountStroops)) + : bid + ? 'Not committed' + : '—'}
+ {bidStatus === 'pending' && ( +

+ Submission is pending or interrupted. Do not retry. +

+ )} + {bidStatus === 'unknown' && ( +
+

+ Submission outcome is unknown. Confirm transaction status before retrying. +

+ {bid?.transactionHash && ( + + Check transaction status + + )} +
+ )} + {bidStatus === 'failed' && ( +

+ Bid was not submitted. The saved secret is safe to retry. +

+ )} +
{phase === 'commit' && !bid && ( + )} + {phase === 'commit' && hasCommittedBid && ( Bid committed — return to reveal it )} - {phase === 'reveal' && bid && !bid.revealed && ( + {phase === 'reveal' && hasCommittedBid && bid && !bid.revealed && ( )} {phase === 'ended' && ( @@ -450,16 +886,19 @@ export default function NamesAuctions() { {pendingAction === `claim:${auction.name}` ? 'Claiming...' : 'Claim name'} )} - {(phase === 'ended' || phase === 'settled') && bid && !isWinner && ( - - )} + {(phase === 'ended' || phase === 'settled') && + hasCommittedBid && + bid && + !isWinner && ( + + )}
); @@ -495,6 +934,14 @@ export default function NamesAuctions() { + {unlocked ? null : ( +
+

+ Set up your encryption passphrase in the vault section above before placing a bid. +

+
+ )} + @@ -504,6 +951,7 @@ export default function NamesAuctions() { value={bidAmount} onChange={(event) => setBidAmount(event.target.value)} placeholder="0.0" + disabled={retryingBid} aria-invalid={Boolean(bidError)} className="mt-2 h-12 w-full border border-outline-variant bg-surface-container px-4 font-heading text-xl text-primary" /> @@ -527,8 +975,9 @@ export default function NamesAuctions() { )}

- Your amount and recovery secret stay in this browser until reveal. Clearing site data - before revealing can make the bid unrecoverable. + Your amount and recovery secret stay encrypted in this browser until reveal. Clearing + site data before revealing can make the bid unrecoverable. Export a backup after + setting your passphrase.

{error && ( @@ -543,13 +992,17 @@ export default function NamesAuctions() { disabled={ parsedBid === null || Boolean(bidError) || - pendingAction === `bid:${selectedAuction.name}` + pendingAction === `bid:${selectedAuction.name}` || + selectedBidCannotRetry || + !unlocked } className="mt-5 h-11 w-full bg-primary font-heading text-[11px] font-semibold uppercase tracking-widest text-surface disabled:opacity-30" > {pendingAction === `bid:${selectedAuction.name}` ? 'Confirm in wallet...' - : 'Place bid'} + : retryingBid + ? 'Retry bid' + : 'Place bid'} diff --git a/src/store/nameWatchlistStore.test.ts b/src/store/nameWatchlistStore.test.ts index c0d09d3..16124ec 100644 --- a/src/store/nameWatchlistStore.test.ts +++ b/src/store/nameWatchlistStore.test.ts @@ -1,4 +1,5 @@ -import { beforeEach, describe, expect, it, vi, afterEach } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { decryptBids } from '@/lib/auctionEncryption'; class MemoryStorage implements Storage { private values = new Map(); @@ -28,98 +29,550 @@ class MemoryStorage implements Storage { } } -class MockWindow { - localStorage: Storage; - private listeners: Map void)[]> = new Map(); +let storage: MemoryStorage; - constructor(storage: Storage) { - this.localStorage = storage; - } +beforeEach(() => { + vi.resetModules(); + storage = new MemoryStorage(); + vi.stubGlobal('localStorage', storage); + vi.stubGlobal('window', { localStorage: storage }); +}); - addEventListener(event: string, listener: (e: StorageEvent) => void) { - if (!this.listeners.has(event)) { - this.listeners.set(event, []); - } - this.listeners.get(event)!.push(listener); - } +async function loadStore() { + return await import('./nameWatchlistStore'); +} + +const legacyPayload = { + version: 0, + state: { + watchedAuctions: [{ name: 'NOVA', endsAt: 1_800_000_000 }], + bids: { + nova: { + name: 'nova', + amountStroops: '25000000', + depositStroops: '25000000', + saltHex: 'ab'.repeat(32), + revealed: false, + }, + }, + }, +}; + +const passphrase = 'Passw0rd!Passw0rd!'; +const iterations = 1000; + +const newBid = { + name: 'nova', + amountStroops: '25000000', + depositStroops: '25000000', + saltHex: 'ab'.repeat(32), + revealed: false, +}; + +describe('name auction watchlist (encrypted recovery)', () => { + it('starts empty when there is no stored vault or legacy data', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + store.inspectStorage(); + const state = useNameWatchlistStore.getState(); + expect(state.getStatus()).toBe('empty'); + expect(state.getBids()).toEqual({}); + expect(state.hasLegacyPlaintext).toBe(false); + expect(state.hasEncryptedVault).toBe(false); + }); + + it('recognizes versioned plaintext watchlist data for safe encrypted migration', async () => { + const { useNameWatchlistStore } = await loadStore(); + localStorage.setItem( + 'wraith-name-auction-watchlist', + JSON.stringify({ + version: 1, + type: 'wraith-versioned-value', + data: { + watchedAuctions: [{ name: 'nova', endsAt: 1_800_000_000 }], + bids: { nova: newBid }, + }, + }), + ); + + useNameWatchlistStore.getState().inspectStorage(); + expect(useNameWatchlistStore.getState().status).toBe('legacy'); + + await useNameWatchlistStore.getState().setPassphrase(passphrase, iterations); + expect(useNameWatchlistStore.getState().bids.nova?.saltHex).toBe(newBid.saltHex); + expect(localStorage.getItem('wraith-name-auction-watchlist')).toContain( + '"type":"wraith-versioned-value"', + ); + expect(localStorage.getItem('wraith-name-auction-watchlist.vault')).not.toContain( + newBid.saltHex, + ); + }); + + it('restores watched auctions without decrypting bids while locked', async () => { + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + storeA.watchAuction({ name: 'nova', endsAt: 1_800_000_000 }); + storeA.lock(); + + // Simulate a page reload: fresh module instance, same localStorage. + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + storeB.inspectStorage(); + const state = StoreB.getState(); + expect(state.getStatus()).toBe('locked'); + expect(state.getBids()).toEqual({}); + expect(state.watchedAuctions).toEqual([{ name: 'nova', endsAt: 1_800_000_000 }]); + expect(state.hasEncryptedVault).toBe(true); + }); + + it('detects existing legacy plaintext as legacy status', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + localStorage.setItem('wraith-name-auction-watchlist', JSON.stringify(legacyPayload)); + store.inspectStorage(); + const state = useNameWatchlistStore.getState(); + expect(state.getStatus()).toBe('legacy'); + expect(state.hasLegacyPlaintext).toBe(true); + expect(state.hasEncryptedVault).toBe(false); + }); + + it('creates an encrypted vault without writing plaintext secrets to localStorage', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + store.inspectStorage(); + await store.setPassphrase(passphrase, iterations); + const state = useNameWatchlistStore.getState(); + expect(state.getStatus()).toBe('unlocked'); + + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).not.toBeNull(); + const parsed = JSON.parse(raw!); + expect(parsed.version).toBe(1); + expect(parsed.encryptedBids).toBeDefined(); + + // Plaintext recovery secrets must never appear in localStorage. + expect(raw).not.toContain(passphrase); + }); + + it('persists a new bid only in encrypted form', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + await store.setPassphrase(passphrase, iterations); + await store.saveBid(newBid); + const state = useNameWatchlistStore.getState(); + expect(state.getBids().nova?.amountStroops).toBe('25000000'); + expect(state.getBids().nova?.saltHex).toBe('ab'.repeat(32)); + + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).not.toContain('25000000'); + expect(raw).not.toContain('abababab'); + const parsed = JSON.parse(raw!); + expect(parsed.encryptedBids).toBeDefined(); + }); + + it('rejects saving a bid while the vault is locked', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + store.inspectStorage(); + await expect(store.saveBid(newBid)).rejects.toThrow(/locked/i); + }); + + it('unlocks an existing vault after a simulated reload', async () => { + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + await storeA.saveBid(newBid); + + // Simulate a page reload: fresh module instance, same localStorage. + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + storeB.inspectStorage(); + let state = StoreB.getState(); + expect(state.getStatus()).toBe('locked'); + expect(state.getBids()).toEqual({}); + + await storeB.unlock(passphrase); + state = StoreB.getState(); + expect(state.getStatus()).toBe('unlocked'); + expect(state.getBids().nova?.amountStroops).toBe('25000000'); + expect(state.getBids().nova?.saltHex).toBe('ab'.repeat(32)); + }); + + it('rejects an incorrect passphrase on unlock', async () => { + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + storeB.inspectStorage(); + await expect(storeB.unlock('wrong-passphrase!')).rejects.toThrow(); + const state = StoreB.getState(); + expect(state.getStatus()).toBe('error'); + expect(state.getBids()).toEqual({}); + }); + + it('migrates legacy plaintext bids into an encrypted vault', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + localStorage.setItem('wraith-name-auction-watchlist', JSON.stringify(legacyPayload)); + store.inspectStorage(); + let state = useNameWatchlistStore.getState(); + expect(state.getStatus()).toBe('legacy'); + + await store.setPassphrase(passphrase, iterations); + state = useNameWatchlistStore.getState(); + expect(state.getStatus()).toBe('unlocked'); + expect(state.getBids().nova?.amountStroops).toBe('25000000'); + expect(state.getBids().nova?.saltHex).toBe('ab'.repeat(32)); + + // The encrypted replacement exists in the active record. + expect(state.hasEncryptedVault).toBe(true); + + // Legacy plaintext remains in the canonical store until explicitly cleared. + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).toContain('"version":0'); + expect(raw).toContain('25000000'); + }); + + it('migration keeps legacy plaintext until explicitly cleared', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + localStorage.setItem('wraith-name-auction-watchlist', JSON.stringify(legacyPayload)); + store.inspectStorage(); + await store.setPassphrase(passphrase, iterations); + + // Legacy plaintext still present in localStorage (not yet cleared). + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).toContain('"version":0'); + + // Clear is an explicit user action; it requires a verified encrypted replacement. + const cleared = await store.clearLegacy(); + expect(cleared).toBe(true); + + const state = useNameWatchlistStore.getState(); + expect(state.hasLegacyPlaintext).toBe(false); + + const after = localStorage.getItem('wraith-name-auction-watchlist'); + const parsed = JSON.parse(after!); + expect(parsed.version).toBe(1); + expect(parsed.encryptedBids).toBeDefined(); + expect(after).not.toContain('"version":0'); + expect(after).not.toContain('25000000'); + }); + + it('refuses to clear legacy plaintext without a verified encrypted replacement', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + localStorage.setItem('wraith-name-auction-watchlist', JSON.stringify(legacyPayload)); + store.inspectStorage(); + + await expect(store.clearLegacy()).rejects.toThrow(/encrypted replacement/i); + // Original legacy data is untouched. + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).toBe(JSON.stringify(legacyPayload)); + }); + + it('clearing legacy is idempotent and safe to repeat', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + localStorage.setItem('wraith-name-auction-watchlist', JSON.stringify(legacyPayload)); + store.inspectStorage(); + await store.setPassphrase(passphrase, iterations); + await store.clearLegacy(); - removeEventListener(event: string, listener: (e: StorageEvent) => void) { - const eventListeners = this.listeners.get(event); - if (eventListeners) { - const index = eventListeners.indexOf(listener); - if (index > -1) { - eventListeners.splice(index, 1); - } - } + // Second attempt: nothing legacy left, safe no-op. + const again = await store.clearLegacy(); + expect(again).toBe(false); + }); + + it('migration failure leaves the original plaintext intact', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + localStorage.setItem('wraith-name-auction-watchlist', JSON.stringify(legacyPayload)); + store.inspectStorage(); + + // Passphrase too short -> setup fails before touching storage. + await expect(store.setPassphrase('short', iterations)).rejects.toThrow(/at least 8/); + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).toBe(JSON.stringify(legacyPayload)); + }); + + it('exports a documented, versioned backup and recovers from it', async () => { + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + await storeA.saveBid(newBid); + + const backup = await storeA.exportBackup(); + const parsed = JSON.parse(backup); + expect(parsed.type).toBe('wraith-name-auction-backup'); + expect(parsed.version).toBe(1); + expect(parsed.vault.version).toBe(1); + + // Fresh session: import the backup and recover into an empty vault. + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + localStorage.clear(); + await storeB.importBackup(backup, passphrase); + const state = StoreB.getState(); + expect(state.getStatus()).toBe('unlocked'); + expect(state.getBids().nova?.amountStroops).toBe('25000000'); + expect(state.getBids().nova?.saltHex).toBe('ab'.repeat(32)); + }); + + it('rejects backup import with an incorrect passphrase', async () => { + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + const backup = await storeA.exportBackup(); + + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + await expect(storeB.importBackup(backup, 'wrong-passphrase!')).rejects.toThrow(); + const state = StoreB.getState(); + expect(state.getStatus()).toBe('error'); + }); + + it('rejects import of a corrupted backup', async () => { + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + const backup = JSON.parse(await storeA.exportBackup()); + backup.vault.data = 'AAAA'; // corrupted + const corrupted = JSON.stringify(backup); + + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + await expect(storeB.importBackup(corrupted, passphrase)).rejects.toThrow(); + }); + + it('lock clears decrypted bids from memory', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + await store.setPassphrase(passphrase, iterations); + await store.saveBid(newBid); + let state = useNameWatchlistStore.getState(); + expect(state.getBids().nova).toBeDefined(); + + store.lock(); + state = useNameWatchlistStore.getState(); + expect(state.getStatus()).toBe('locked'); + expect(state.getBids()).toEqual({}); + }); + + it('marks a bid revealed and persists the change encrypted', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + await store.setPassphrase(passphrase, iterations); + await store.saveBid(newBid); + expect(useNameWatchlistStore.getState().getBids().nova?.revealed).toBe(false); + + await store.markBidRevealed('nova'); + expect(useNameWatchlistStore.getState().getBids().nova?.revealed).toBe(true); + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).not.toContain('25000000'); + }); + + it('removes a bid and persists the change encrypted', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + await store.setPassphrase(passphrase, iterations); + await store.saveBid(newBid); + await store.removeBid('nova'); + expect(useNameWatchlistStore.getState().getBids().nova).toBeUndefined(); + }); + + it('watch and unwatch track auctions without exposing bid secrets', async () => { + const { useNameWatchlistStore } = await loadStore(); + const store = useNameWatchlistStore.getState(); + store.inspectStorage(); + await store.setPassphrase(passphrase, iterations); + + store.watchAuction({ name: 'nova', endsAt: 1_800_000_000 }); + expect(useNameWatchlistStore.getState().getBids()).toEqual({}); + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).not.toContain('25000000'); + + store.unwatchAuction('nova'); + }); +}); + +describe('import backup safety (no silent vault destruction)', () => { + const currentBid = { + name: 'alpha', + amountStroops: '10000000', + depositStroops: '10000000', + saltHex: 'cd'.repeat(32), + revealed: false, + }; + const importedBid = { + name: 'beta', + amountStroops: '20000000', + depositStroops: '20000000', + saltHex: 'ef'.repeat(32), + revealed: false, + }; + + async function exportImportedBackup(): Promise { + const { useNameWatchlistStore: StoreC } = await loadStore(); + const storeC = StoreC.getState(); + await storeC.setPassphrase(passphrase, iterations); + await storeC.saveBid(importedBid); + return await storeC.exportBackup(); } - dispatchEvent(event: StorageEvent) { - const eventListeners = this.listeners.get(event.type); - if (eventListeners) { - eventListeners.forEach((listener) => listener(event)); - } + async function establishCurrentVault(): Promise { + // Start from a clean slate: the exported backup may have left a vault in + // shared localStorage, which setPassphrase would otherwise reject. + localStorage.clear(); + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + await storeA.saveBid(currentBid); } -} -describe('name auction watchlist', () => { - beforeEach(() => { - vi.resetModules(); - const storage = new MemoryStorage(); - const mockWindow = new MockWindow(storage); - vi.stubGlobal('localStorage', storage); - vi.stubGlobal('window', mockWindow); + it('refuses to replace a non-empty current vault without explicit confirmation', async () => { + const backup = await exportImportedBackup(); + await establishCurrentVault(); + + // Fresh session (simulated reload): current vault still holds currentBid. + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + storeB.inspectStorage(); + expect(storeB.getStatus()).toBe('locked'); + + await expect(storeB.importBackup(backup, passphrase)).rejects.toThrow( + /current vault|replacement/i, + ); + + // Current vault untouched: currentBid still recoverable after unlock. + await storeB.unlock(passphrase); + expect(storeB.getBids().alpha?.saltHex).toBe(currentBid.saltHex); + expect(storeB.getBids().beta).toBeUndefined(); + }); + + it('replaces the current vault only when replacement is explicitly confirmed', async () => { + const backup = await exportImportedBackup(); + await establishCurrentVault(); + + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + storeA.inspectStorage(); + expect(storeA.getStatus()).toBe('locked'); + + await storeA.importBackup(backup, passphrase, { replace: true }); + const state = StoreA.getState(); + expect(state.getStatus()).toBe('unlocked'); + expect(state.getBids().beta?.saltHex).toBe(importedBid.saltHex); + expect(state.getBids().alpha).toBeUndefined(); + + // The replacement is durable across a simulated reload. + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + storeB.inspectStorage(); + await storeB.unlock(passphrase); + expect(storeB.getBids().beta?.amountStroops).toBe('20000000'); + expect(storeB.getBids().alpha).toBeUndefined(); + }); + + it('preserves the current vault when import uses the wrong passphrase', async () => { + const backup = await exportImportedBackup(); + await establishCurrentVault(); + + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + storeA.inspectStorage(); + + await expect( + storeA.importBackup(backup, 'wrong-passphrase!', { replace: true }), + ).rejects.toThrow(); + // Current vault preserved: unlock with the original passphrase still recovers currentBid. + await storeA.unlock(passphrase); + expect(storeA.getBids().alpha?.saltHex).toBe(currentBid.saltHex); + expect(storeA.getBids().beta).toBeUndefined(); }); - afterEach(async () => { - const { cleanupNameWatchlistSync } = await import('./nameWatchlistStore'); - cleanupNameWatchlistSync(); + it('preserves the current vault when importing a corrupted backup', async () => { + const parsed = JSON.parse(await exportImportedBackup()); + parsed.vault.data = 'AAAA'; // corrupted ciphertext + const corrupted = JSON.stringify(parsed); + await establishCurrentVault(); + + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + storeA.inspectStorage(); + + await expect(storeA.importBackup(corrupted, passphrase, { replace: true })).rejects.toThrow(); + // Current vault preserved. + await storeA.unlock(passphrase); + expect(storeA.getBids().alpha?.saltHex).toBe(currentBid.saltHex); + expect(storeA.getBids().beta).toBeUndefined(); }); +}); + +describe('saveBid read-back verification (submission gate)', () => { + const gateBid = { + name: 'gate', + amountStroops: '7000000', + depositStroops: '7000000', + saltHex: '01'.repeat(32), + revealed: false, + }; - it('restores watched auctions and sealed bid recovery data', async () => { - const { useNameWatchlistStore, initNameWatchlistSync } = await import('./nameWatchlistStore'); - initNameWatchlistSync(); + it('persists a decryptable, verified recovery record before it can be trusted for submission', async () => { + const { useNameWatchlistStore } = await loadStore(); const store = useNameWatchlistStore.getState(); - store.watchAuction({ name: 'NOVA', endsAt: 1_800_000_000 }); - store.saveBid({ - name: 'NOVA', - amountStroops: '25000000', - depositStroops: '25000000', - saltHex: 'ab'.repeat(32), - revealed: false, - }); + await store.setPassphrase(passphrase, iterations); + await store.saveBid(gateBid); - const persisted = localStorage.getItem('wraith-name-auction-watchlist'); - expect(persisted).not.toBeNull(); - useNameWatchlistStore.setState({ watchedAuctions: [], bids: {} }); - localStorage.setItem('wraith-name-auction-watchlist', persisted!); - await useNameWatchlistStore.persist.rehydrate(); - - expect(useNameWatchlistStore.getState().watchedAuctions).toEqual([ - { name: 'nova', endsAt: 1_800_000_000 }, - ]); - expect(useNameWatchlistStore.getState().bids.nova).toMatchObject({ - amountStroops: '25000000', - saltHex: 'ab'.repeat(32), - revealed: false, - }); + // The persisted vault must be decryptable and contain the exact recovery + // material (salt + amount) — this is the verification gate that must pass + // before an on-chain commit is ever submitted. + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).not.toBeNull(); + const parsed = JSON.parse(raw!); + expect(parsed.encryptedBids).toBeDefined(); + // Plaintext recovery secrets must never appear in localStorage. + expect(raw).not.toContain(gateBid.saltHex); + expect(raw).not.toContain(gateBid.amountStroops); + + const decrypted = await decryptBids(passphrase, parsed.encryptedBids); + expect(decrypted.gate?.saltHex).toBe(gateBid.saltHex); + expect(decrypted.gate?.amountStroops).toBe(gateBid.amountStroops); + expect(decrypted.gate?.revealed).toBe(false); }); - it('recovers from corrupt and unknown-version persisted data', async () => { - localStorage.setItem('wraith-name-auction-watchlist', '{not-json'); - const { useNameWatchlistStore, initNameWatchlistSync } = await import('./nameWatchlistStore'); - initNameWatchlistSync(); - await useNameWatchlistStore.persist.rehydrate(); - expect(useNameWatchlistStore.getState().watchedAuctions).toEqual([]); - expect(useNameWatchlistStore.getState().bids).toEqual({}); + it('persists submission status transitions with the encrypted recovery secret', async () => { + const { useNameWatchlistStore: StoreA } = await loadStore(); + const storeA = StoreA.getState(); + await storeA.setPassphrase(passphrase, iterations); + await storeA.saveBid({ ...gateBid, submissionStatus: 'pending' }); + await storeA.updateBidSubmission('gate', 'committed', 'stellar-hash'); - localStorage.setItem( - 'wraith-name-auction-watchlist', - JSON.stringify({ - version: 99, - state: { watchedAuctions: [{ name: 'future', endsAt: 1 }], bids: {} }, - }), - ); - await useNameWatchlistStore.persist.rehydrate(); - expect(useNameWatchlistStore.getState().watchedAuctions).toEqual([]); + expect(StoreA.getState().getBids().gate).toMatchObject({ + saltHex: gateBid.saltHex, + submissionStatus: 'committed', + transactionHash: 'stellar-hash', + }); + const raw = localStorage.getItem('wraith-name-auction-watchlist'); + expect(raw).not.toContain(gateBid.saltHex); + + const { useNameWatchlistStore: StoreB } = await loadStore(); + const storeB = StoreB.getState(); + storeB.inspectStorage(); + await storeB.unlock(passphrase); + expect(storeB.getBids().gate).toMatchObject({ + saltHex: gateBid.saltHex, + submissionStatus: 'committed', + transactionHash: 'stellar-hash', + }); + await storeB.updateBidSubmission('gate', 'unknown', 'possible-hash'); + expect(storeB.getBids().gate).toMatchObject({ + saltHex: gateBid.saltHex, + submissionStatus: 'unknown', + transactionHash: 'possible-hash', + }); }); }); diff --git a/src/store/nameWatchlistStore.tsx b/src/store/nameWatchlistStore.tsx index a450779..0afb8fe 100644 --- a/src/store/nameWatchlistStore.tsx +++ b/src/store/nameWatchlistStore.tsx @@ -1,158 +1,587 @@ import { create } from 'zustand'; -import { persist, type PersistStorage } from 'zustand/middleware'; + +import type { AuctionBid } from '@/lib/auctionEncryption'; import { - MAX_COLLECTION_ITEMS, - isBoundedString, - isFiniteTimestamp, - readVersionedValue, - writeVersioned, -} from '../lib/versionedStorage'; + AUCTION_STORAGE_KEY, + AUCTION_VAULT_VERSION, + DEFAULT_PBKDF2_ITERATIONS, + createBackup, + decryptBids, + deriveKey, + encryptBids, + isValidVault, + parseBackup, + type EncryptedBidVault, +} from '@/lib/auctionEncryption'; + +/** + * Staging/active storage key for the encrypted bid recovery vault while legacy + * plaintext has not yet been explicitly cleared. While legacy plaintext is + * present, the canonical key (`wraith-name-auction-watchlist`) holds the + * legacy v0 payload untouched and the active record (watched auctions + + * encrypted bids) is staged under this vault key. Once the user explicitly + * clears the legacy plaintext, the active record is written to the canonical + * key and this vault key is removed. + */ +export const VAULT_STORAGE_KEY = `${AUCTION_STORAGE_KEY}.vault`; + +export type { AuctionBid }; export interface WatchedNameAuction { name: string; endsAt: number; } -export interface LocalAuctionBid { - name: string; - amountStroops: string; - depositStroops: string; - saltHex: string; - revealed: boolean; +export type AuctionStatus = 'empty' | 'legacy' | 'locked' | 'unlocked' | 'error'; + +interface ActiveRecord { + watchedAuctions: WatchedNameAuction[]; + encryptedBids: EncryptedBidVault | null; } interface NameWatchlistState { watchedAuctions: WatchedNameAuction[]; - bids: Record; + bids: Record; + status: AuctionStatus; + error: string | null; + hasLegacyPlaintext: boolean; + hasEncryptedVault: boolean; + inspectStorage: () => void; + setPassphrase: (passphrase: string, iterations?: number) => Promise; + unlock: (passphrase: string) => Promise; + lock: () => void; watchAuction: (auction: WatchedNameAuction) => void; unwatchAuction: (name: string) => void; - saveBid: (bid: LocalAuctionBid) => void; - markBidRevealed: (name: string) => void; - removeBid: (name: string) => void; -} - -type PersistedWatchlist = Pick; - -function isPersistedWatchlist(value: unknown): value is PersistedWatchlist { - if (typeof value !== 'object' || value === null) return false; - const state = value as Record; - if ( - !Array.isArray(state.watchedAuctions) || - typeof state.bids !== 'object' || - state.bids === null - ) { - return false; + saveBid: (bid: AuctionBid) => Promise; + updateBidSubmission: ( + name: string, + submissionStatus: NonNullable, + transactionHash?: string, + ) => Promise; + markBidRevealed: (name: string) => Promise; + removeBid: (name: string) => Promise; + exportBackup: () => Promise; + importBackup: ( + backup: string, + passphrase: string, + options?: { replace?: boolean }, + ) => Promise; + clearLegacy: () => Promise; + getBids: () => Record; + getStatus: () => AuctionStatus; +} + +const normalizeName = (name: string) => name.trim().toLowerCase(); + +/** + * In-memory derived encryption key, held only while the vault is unlocked. + * Cleared on lock, page reload, or logout. Never persisted to localStorage. + */ +let activeKey: CryptoKey | null = null; +let activeKdfMeta: { salt: string; iterations: number } | null = null; + +function clearActiveKey() { + activeKey = null; + activeKdfMeta = null; +} + +function readRaw(key: string): T | null { + try { + const raw = localStorage.getItem(key); + if (raw === null) return null; + return JSON.parse(raw) as T; + } catch { + return null; } - const watched = state.watchedAuctions as unknown[]; - const bids = state.bids as Record; - return ( - watched.length <= MAX_COLLECTION_ITEMS && - Object.keys(bids).length <= MAX_COLLECTION_ITEMS && - watched.every((item) => { - if (typeof item !== 'object' || item === null) return false; - const auction = item as Record; - return ( - isBoundedString(auction.name, 128) && - auction.name.length > 0 && - isFiniteTimestamp(auction.endsAt) - ); - }) && - Object.values(bids).every((item) => { - if (typeof item !== 'object' || item === null) return false; - const bid = item as Record; - return ( - isBoundedString(bid.name, 128) && - isBoundedString(bid.amountStroops, 80) && - isBoundedString(bid.depositStroops, 80) && - isBoundedString(bid.saltHex, 256) && - typeof bid.revealed === 'boolean' - ); - }) +} + +function writeRaw(key: string, value: unknown): void { + localStorage.setItem(key, JSON.stringify(value)); +} + +function removeRaw(key: string): void { + localStorage.removeItem(key); +} + +/** Legacy zustand-persist format: { version: 0, state: { watchedAuctions, bids } } */ +function readLegacy(): { + bids: Record; + watchedAuctions: WatchedNameAuction[]; +} | null { + const raw = readRaw>(AUCTION_STORAGE_KEY); + if (!raw) return null; + const legacyState = + raw.version === 0 && raw.state && typeof raw.state === 'object' + ? (raw.state as Record) + : raw.version === AUCTION_VAULT_VERSION && raw.data && typeof raw.data === 'object' + ? (raw.data as Record) + : null; + if (!legacyState) return null; + const watchedAuctions = Array.isArray(legacyState.watchedAuctions) + ? legacyState.watchedAuctions + : []; + const bids = legacyState.bids; + if (!bids || typeof bids !== 'object' || Array.isArray(bids)) { + return { bids: {}, watchedAuctions }; + } + return { bids: bids as Record, watchedAuctions }; +} + +/** New v1 format: { version: 1, watchedAuctions, encryptedBids: EncryptedBidVault | null } */ +function readCanonicalV1(): ActiveRecord | null { + const raw = readRaw<{ + version: number; + data?: unknown; + watchedAuctions?: unknown; + encryptedBids?: unknown; + }>(AUCTION_STORAGE_KEY); + if (!raw || raw.version !== AUCTION_VAULT_VERSION || raw.data) return null; + const watchedAuctions = Array.isArray(raw.watchedAuctions) ? raw.watchedAuctions : []; + const encryptedBids = isValidVault(raw.encryptedBids) ? raw.encryptedBids : null; + return { watchedAuctions, encryptedBids }; +} + +/** Active record staged under the vault key (pre-clear migration). */ +function readVaultKeyRecord(): ActiveRecord | null { + const raw = readRaw(VAULT_STORAGE_KEY); + if (!raw) return null; + let vault: EncryptedBidVault | null = null; + let watchedAuctions: unknown = null; + try { + const parsed = parseBackup(raw); + vault = parsed.vault; + } catch { + if (isValidVault(raw)) { + vault = raw as EncryptedBidVault; + } else if (raw && typeof raw === 'object' && 'encryptedBids' in raw) { + const rec = raw as { watchedAuctions?: unknown; encryptedBids?: unknown }; + vault = isValidVault(rec.encryptedBids) ? rec.encryptedBids : null; + watchedAuctions = rec.watchedAuctions; + } + } + if (!vault) return null; + return { + watchedAuctions: Array.isArray(watchedAuctions) ? watchedAuctions : [], + encryptedBids: vault, + }; +} + +/** The currently active record: vault key (pre-clear) else canonical v1 else legacy. */ +function readActive(): (ActiveRecord & { isLegacy: boolean }) | null { + const vault = readVaultKeyRecord(); + if (vault) return { ...vault, isLegacy: false }; + const canonical = readCanonicalV1(); + if (canonical) return { ...canonical, isLegacy: false }; + const legacy = readLegacy(); + if (legacy) + return { watchedAuctions: legacy.watchedAuctions, encryptedBids: null, isLegacy: true }; + return null; +} + +function activeEncryptedVault(): EncryptedBidVault | null { + const active = readActive(); + return active?.encryptedBids ?? null; +} + +function writeActive(record: ActiveRecord): void { + // While legacy plaintext exists, stage under the vault key and leave the + // canonical legacy untouched until explicitly cleared. + if (readLegacy()) { + writeRaw(VAULT_STORAGE_KEY, record); + } else { + writeRaw(AUCTION_STORAGE_KEY, { version: AUCTION_VAULT_VERSION, ...record }); + } +} + +function toBase64(bytes: Uint8Array): string { + let binary = ''; + for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]); + if (typeof btoa === 'function') { + return btoa(binary); + } + return Buffer.from(binary, 'binary').toString('base64'); +} + +function fromBase64(input: string): Uint8Array { + if (typeof atob === 'function') { + const binary = atob(input); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes; + } + return Buffer.from(input, 'base64') as unknown as Uint8Array; +} + +/** Re-encrypt bids using the in-memory derived key with a fresh IV every time. */ +async function sealBids(bids: Record): Promise { + if (!activeKey || !activeKdfMeta) { + throw new Error('Vault is not unlocked. Set a passphrase before saving bids.'); + } + const subtle = globalThis.crypto.subtle; + const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); + const ciphertext = await subtle.encrypt( + { name: 'AES-GCM', iv: iv as unknown as BufferSource }, + activeKey, + new TextEncoder().encode(JSON.stringify(bids)), ); + return { + version: AUCTION_VAULT_VERSION, + kdf: { + algorithm: 'PBKDF2', + hash: 'SHA-256', + iterations: activeKdfMeta.iterations, + salt: activeKdfMeta.salt, + }, + cipher: { algorithm: 'AES-GCM', iv: toBase64(iv), keyLength: 256 }, + data: toBase64(new Uint8Array(ciphertext)), + }; } -const watchlistStorage: PersistStorage = { - getItem: (name) => { - const state = readVersionedValue( - localStorage, - name, - isPersistedWatchlist, - (value) => { - const legacy = value as { state?: unknown }; - return legacy.state; - }, - ); - return state ? { state: state as NameWatchlistState, version: 1 } : null; - }, - setItem: (name, value) => - writeVersioned(localStorage, name, { - watchedAuctions: value.state.watchedAuctions, - bids: value.state.bids, - }), - removeItem: (name) => localStorage.removeItem(name), -}; +async function persistEncrypted( + watchedAuctions: WatchedNameAuction[], + bids: Record, +): Promise { + const vault = await sealBids(bids); + writeActive({ watchedAuctions, encryptedBids: vault }); +} -const normalizeName = (name: string) => name.trim().toLowerCase(); +/** Decrypt a stored vault using the in-memory derived key (vault must be unlocked). */ +async function decryptVaultWithActiveKey( + vault: EncryptedBidVault, +): Promise> { + if (!activeKey) { + throw new Error('Vault is not unlocked.'); + } + const subtle = globalThis.crypto.subtle; + const iv = fromBase64(vault.cipher.iv); + const ciphertext = new Uint8Array(fromBase64(vault.data)); + const decrypted = await subtle.decrypt( + { name: 'AES-GCM', iv: iv as unknown as BufferSource }, + activeKey, + ciphertext, + ); + const plaintext = new TextDecoder().decode(decrypted); + let parsed: unknown; + try { + parsed = JSON.parse(plaintext); + } catch { + throw new Error('Persisted recovery record is not valid JSON.'); + } + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new Error('Persisted recovery record is not a bids record.'); + } + return parsed as Record; +} -export const useNameWatchlistStore = create()( - persist( - (set) => ({ - watchedAuctions: [], +/** Read back the persisted vault and verify every expected bid is durably stored. */ +async function verifyStoredBids(expected: Record): Promise { + if (!activeKey) { + throw new Error('Vault is not unlocked; cannot verify persisted recovery record.'); + } + const active = readActive(); + const vault = active?.encryptedBids ?? null; + if (!vault) { + throw new Error('No encrypted vault found after persisting bid.'); + } + const decrypted = await decryptVaultWithActiveKey(vault); + for (const [name, bid] of Object.entries(expected)) { + const stored = decrypted[name]; + if (!stored || JSON.stringify(stored) !== JSON.stringify(bid)) { + throw new Error(`Persisted recovery record verification failed for ${name}.`); + } + } +} + +export const useNameWatchlistStore = create((set, get) => ({ + watchedAuctions: [], + bids: {}, + status: 'empty', + error: null, + hasLegacyPlaintext: false, + hasEncryptedVault: false, + + inspectStorage() { + const active = readActive(); + const hasLegacy = Boolean(active?.isLegacy); + const hasEncrypted = Boolean(active?.encryptedBids); + set({ + hasLegacyPlaintext: hasLegacy, + hasEncryptedVault: hasEncrypted, + watchedAuctions: active?.watchedAuctions ?? [], bids: {}, - watchAuction: (auction: WatchedNameAuction) => - set((state: NameWatchlistState) => { - const name = normalizeName(auction.name); - return { - watchedAuctions: [ - ...state.watchedAuctions.filter((item) => item.name !== name), - { ...auction, name }, - ], - }; - }), - unwatchAuction: (name: string) => - set((state: NameWatchlistState) => ({ - watchedAuctions: state.watchedAuctions.filter( - (item) => item.name !== normalizeName(name), - ), - })), - saveBid: (bid: LocalAuctionBid) => - set((state: NameWatchlistState) => { - const name = normalizeName(bid.name); - return { bids: { ...state.bids, [name]: { ...bid, name } } }; - }), - markBidRevealed: (name: string) => - set((state: NameWatchlistState) => { - const key = normalizeName(name); - const bid = state.bids[key]; - if (!bid) return state; - return { bids: { ...state.bids, [key]: { ...bid, revealed: true } } }; - }), - removeBid: (name: string) => - set((state: NameWatchlistState) => { - const bids = { ...state.bids }; - delete bids[normalizeName(name)]; - return { bids }; - }), - }), - { name: 'wraith-name-auction-watchlist', storage: watchlistStorage, version: 1 }, - ), -); - -let storageListener: ((e: StorageEvent) => void) | null = null; + status: hasLegacy ? 'legacy' : hasEncrypted ? 'locked' : 'empty', + error: null, + }); + }, + + async setPassphrase(passphrase: string, iterations: number = DEFAULT_PBKDF2_ITERATIONS) { + if (!passphrase || passphrase.length < 8) { + throw new Error('Passphrase must be at least 8 characters.'); + } + set({ error: null }); + clearActiveKey(); + try { + const legacy = readLegacy(); + const existingVault = activeEncryptedVault(); + + if (existingVault && !legacy) { + throw new Error('An encrypted vault already exists. Use Unlock instead.'); + } + + let bidsToEncrypt: Record; + let watchedAuctions = get().watchedAuctions; + + if (legacy) { + // Migration: encrypt the legacy plaintext bids. + bidsToEncrypt = legacy.bids; + watchedAuctions = legacy.watchedAuctions; + } else { + bidsToEncrypt = {}; + } + + const vault = await encryptBids(passphrase, bidsToEncrypt, iterations); + // Verify the replacement can be decrypted back before trusting it. + const verified = await decryptBids(passphrase, vault); + if (JSON.stringify(verified) !== JSON.stringify(bidsToEncrypt)) { + throw new Error('Encrypted replacement failed verification.'); + } + + const salt = fromBase64(vault.kdf.salt); + activeKey = await deriveKey(passphrase, salt, vault.kdf.iterations); + activeKdfMeta = { salt: vault.kdf.salt, iterations: vault.kdf.iterations }; + + writeActive({ watchedAuctions, encryptedBids: vault }); + + set({ + bids: bidsToEncrypt, + watchedAuctions, + status: 'unlocked', + error: null, + hasLegacyPlaintext: Boolean(legacy), + hasEncryptedVault: true, + }); + } catch (err) { + clearActiveKey(); + const message = err instanceof Error ? err.message : 'Failed to set up encryption.'; + set({ status: 'error', error: message }); + throw err; + } + }, + + async unlock(passphrase) { + set({ error: null }); + clearActiveKey(); + try { + const vault = activeEncryptedVault(); + if (!vault) { + throw new Error('No encrypted vault to unlock.'); + } + const decrypted = await decryptBids(passphrase, vault); + const salt = fromBase64(vault.kdf.salt); + activeKey = await deriveKey(passphrase, salt, vault.kdf.iterations); + activeKdfMeta = { salt: vault.kdf.salt, iterations: vault.kdf.iterations }; + set({ + bids: decrypted, + watchedAuctions: readActive()?.watchedAuctions ?? [], + status: 'unlocked', + error: null, + hasEncryptedVault: true, + }); + } catch (err) { + clearActiveKey(); + const message = err instanceof Error ? err.message : 'Unable to unlock vault.'; + set({ status: 'error', error: message }); + throw err; + } + }, + + lock() { + clearActiveKey(); + set({ bids: {}, status: 'locked', error: null }); + }, + + watchAuction(auction) { + const name = normalizeName(auction.name); + const watched = [ + ...get().watchedAuctions.filter((item) => item.name !== name), + { ...auction, name }, + ]; + const active = readActive(); + writeActive({ watchedAuctions: watched, encryptedBids: active?.encryptedBids ?? null }); + set({ watchedAuctions: watched }); + }, + + unwatchAuction(name) { + const watched = get().watchedAuctions.filter((item) => item.name !== normalizeName(name)); + const active = readActive(); + writeActive({ watchedAuctions: watched, encryptedBids: active?.encryptedBids ?? null }); + set({ watchedAuctions: watched }); + }, + + async saveBid(bid) { + if (get().status !== 'unlocked') { + throw new Error('Vault is locked. Unlock with your passphrase before saving a bid.'); + } + const name = normalizeName(bid.name); + const bids = { ...get().bids, [name]: { ...bid, name } }; + const watched = [...get().watchedAuctions]; + if (!watched.some((item) => item.name === name)) { + watched.push({ name, endsAt: 0 }); + } + await persistEncrypted(watched, bids); + // Read back and verify the recovery record is durably stored and decryptable + // before it is trusted for on-chain submission. + await verifyStoredBids(bids); + set({ bids, watchedAuctions: watched }); + }, + + async updateBidSubmission(name, submissionStatus, transactionHash) { + if (get().status !== 'unlocked') { + throw new Error('Vault is locked. Unlock with your passphrase before updating a bid.'); + } + const key = normalizeName(name); + const bid = get().bids[key]; + if (!bid) { + throw new Error(`No recovery record exists for ${key}.`); + } + const updatedBid: AuctionBid = { ...bid, submissionStatus }; + if ( + (submissionStatus === 'committed' || + submissionStatus === 'unknown' || + submissionStatus === 'pending') && + transactionHash + ) { + updatedBid.transactionHash = transactionHash; + } else { + delete updatedBid.transactionHash; + } + const bids = { ...get().bids, [key]: updatedBid }; + await persistEncrypted(get().watchedAuctions, bids); + await verifyStoredBids(bids); + set({ bids }); + }, + + async markBidRevealed(name) { + if (get().status !== 'unlocked') { + throw new Error('Vault is locked. Unlock with your passphrase before revealing a bid.'); + } + const key = normalizeName(name); + const bid = get().bids[key]; + if (!bid) return; + const bids = { ...get().bids, [key]: { ...bid, revealed: true } }; + await persistEncrypted(get().watchedAuctions, bids); + set({ bids }); + }, + + async removeBid(name) { + if (get().status !== 'unlocked') { + throw new Error('Vault is locked. Unlock with your passphrase before removing a bid.'); + } + const key = normalizeName(name); + const bids = { ...get().bids }; + delete bids[key]; + await persistEncrypted(get().watchedAuctions, bids); + set({ bids }); + }, + + async exportBackup() { + const vault = activeEncryptedVault(); + if (!vault) { + throw new Error('No encrypted vault to back up.'); + } + return JSON.stringify(createBackup(vault)); + }, + + async importBackup(backup, passphrase, { replace = false } = {}) { + if (!passphrase || passphrase.length < 8) { + throw new Error('Passphrase must be at least 8 characters.'); + } + set({ error: null }); + + // Refuse to replace a non-empty current vault without explicit confirmation. + // This check runs before any decryption or key derivation so the current + // vault and its in-memory key are preserved on refusal. + const currentVault = activeEncryptedVault(); + if (currentVault && !replace) { + const message = + 'Current vault already contains bid recovery secrets. Export your current vault first, or confirm replacement.'; + set({ status: 'error', error: message }); + throw new Error(message); + } + + clearActiveKey(); + try { + const input: unknown = typeof backup === 'string' ? JSON.parse(backup) : backup; + const parsed = parseBackup(input); + // Decrypt the imported backup to validate the passphrase and ciphertext and + // inspect the recovered bids. This does not touch the current vault. + const decrypted = await decryptBids(passphrase, parsed.vault); + const salt = fromBase64(parsed.vault.kdf.salt); + activeKey = await deriveKey(passphrase, salt, parsed.vault.kdf.iterations); + activeKdfMeta = { salt: parsed.vault.kdf.salt, iterations: parsed.vault.kdf.iterations }; + // Only now, after successful validation, overwrite the active record. + writeActive({ watchedAuctions: [], encryptedBids: parsed.vault }); + set({ + bids: decrypted, + watchedAuctions: [], + status: 'unlocked', + error: null, + hasEncryptedVault: true, + hasLegacyPlaintext: Boolean(readLegacy()), + }); + } catch (err) { + clearActiveKey(); + const message = err instanceof Error ? err.message : 'Failed to import backup.'; + set({ status: 'error', error: message }); + throw err; + } + }, + + async clearLegacy() { + const legacy = readLegacy(); + if (!legacy) { + // Nothing legacy to clear. Safe no-op for repeated attempts. + return false; + } + const vault = activeEncryptedVault(); + if (!vault) { + throw new Error( + 'Cannot clear legacy plaintext: no verified encrypted replacement exists. Back up and migrate first.', + ); + } + const active = readActive(); + // Replace the canonical legacy plaintext with the verified encrypted vault. + writeRaw(AUCTION_STORAGE_KEY, { + version: AUCTION_VAULT_VERSION, + watchedAuctions: active?.watchedAuctions ?? legacy.watchedAuctions, + encryptedBids: vault, + }); + removeRaw(VAULT_STORAGE_KEY); + set({ hasLegacyPlaintext: false, hasEncryptedVault: true }); + return true; + }, + + getBids() { + return get().bids; + }, + + getStatus() { + return get().status; + }, +})); + +let storageListener: ((event: StorageEvent) => void) | null = null; export function initNameWatchlistSync() { if (typeof window === 'undefined' || storageListener !== null) return; if (typeof window.addEventListener !== 'function') return; - storageListener = (e: StorageEvent) => { - if (e.key === 'wraith-name-auction-watchlist') { - if (e.newValue) { - useNameWatchlistStore.persist.rehydrate(); - } else { - useNameWatchlistStore.setState({ watchedAuctions: [], bids: {} }); - } - } else if (e.key === null) { - useNameWatchlistStore.setState({ watchedAuctions: [], bids: {} }); + storageListener = (event) => { + if (event.key === AUCTION_STORAGE_KEY || event.key === VAULT_STORAGE_KEY) { + useNameWatchlistStore.getState().inspectStorage(); + } else if (event.key === null) { + clearActiveKey(); + useNameWatchlistStore.getState().inspectStorage(); } };